internal/waste/waste_test.go
Rama main · réplica de lectura del repositorio original.
package waste
import (
"context"
"errors"
"strconv"
"strings"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/sophotechlabs/spinoza/internal/api"
)
type fakeCluster struct {
pods []*unstructured.Unstructured
replicaSets []*unstructured.Unstructured
podErr error
ownerErr error
asked []api.ObjectRef
}
func (fc *fakeCluster) ListKind(_ context.Context, ref api.ObjectRef) ([]*unstructured.Unstructured, error) {
fc.asked = append(fc.asked, ref)
switch ref.Resource {
case "pods":
return fc.pods, fc.podErr
case "replicasets":
return fc.replicaSets, fc.ownerErr
default:
return nil, fc.ownerErr
}
}
type fakeMeter struct {
reading Reading
err error
calls int
}
func (fm *fakeMeter) Usage(_ context.Context, _ []Ref) (Reading, error) {
fm.calls++
if fm.err != nil {
return Reading{}, fm.err
}
return fm.reading, nil
}
func reads(pods map[string]api.ResourceUsage, source, window string) *fakeMeter {
return &fakeMeter{reading: Reading{Pods: pods, Source: source, Window: window}}
}
func yes() *bool {
value := true
return &value
}
func requesting(cpu, memory string) map[string]any {
asked := map[string]any{}
if cpu != "" {
asked["cpu"] = cpu
}
if memory != "" {
asked["memory"] = memory
}
return map[string]any{"resources": map[string]any{"requests": asked}}
}
func bareContainer() map[string]any {
return map[string]any{"image": "nginx"}
}
func podOf(namespace, name string, containers ...map[string]any) *unstructured.Unstructured {
list := make([]any, 0, len(containers))
for _, one := range containers {
list = append(list, one)
}
return &unstructured.Unstructured{Object: map[string]any{
"apiVersion": "v1",
"kind": "Pod",
"metadata": map[string]any{"namespace": namespace, "name": name},
"spec": map[string]any{"containers": list},
}}
}
func replicaSetOf(namespace, name, owner string) *unstructured.Unstructured {
out := &unstructured.Unstructured{Object: map[string]any{
"apiVersion": "apps/v1",
"kind": "ReplicaSet",
"metadata": map[string]any{"namespace": namespace, "name": name},
}}
if owner != "" {
ownedBy(out, "apps/v1", "Deployment", owner)
}
return out
}
func ownedBy(obj *unstructured.Unstructured, apiVersion, kind, name string) *unstructured.Unstructured {
obj.SetOwnerReferences([]metav1.OwnerReference{{
APIVersion: apiVersion,
Kind: kind,
Name: name,
Controller: yes(),
}})
return obj
}
func rowFor(t *testing.T, rows []api.WasteRow, namespace, name string) api.WasteRow {
t.Helper()
for _, row := range rows {
if row.Namespace == namespace && row.Name == name {
return row
}
}
t.Fatalf("no row for %s/%s in %+v", namespace, name, rows)
return api.WasteRow{}
}
func built(t *testing.T, cluster *fakeCluster, meters ...Meter) api.WasteReport {
t.Helper()
report, err := Build(t.Context(), cluster, Request{Meters: meters})
if err != nil {
t.Fatalf("build: %v", err)
}
return report
}
func TestRequestsAreSummedAcrossEveryContainerAndEveryPod(t *testing.T) {
cluster := &fakeCluster{pods: []*unstructured.Unstructured{
podOf("shop", "web-1", requesting("100m", "128Mi"), requesting("250m", "256Mi")),
podOf("shop", "web-2", requesting("150m", "64Mi")),
}}
report := built(t, cluster)
row := rowFor(t, report.Namespaces, "shop", "")
for _, want := range []struct {
what string
got int64
want int64
}{
{"pods", int64(row.Pods), 2},
{"cpu requested in millicores", row.CPURequested, 500},
{"memory requested in mebibytes", row.MemRequested, 448},
} {
if want.got != want.want {
t.Errorf("%s = %d, want %d", want.what, want.got, want.want)
}
}
}
func TestAPodThatReservesNothingIsCountedAndNamedRatherThanReadAsZero(t *testing.T) {
cluster := &fakeCluster{pods: []*unstructured.Unstructured{
podOf("shop", "web-1", requesting("400m", "512Mi")),
podOf("shop", "loose", bareContainer()),
}}
usage := map[string]api.ResourceUsage{
"shop/web-1": {CPUMilli: 100, MemoryMi: 128},
"shop/loose": {CPUMilli: 90, MemoryMi: 200},
}
report := built(t, cluster, reads(usage, "metrics-server", "right now"))
row := rowFor(t, report.Namespaces, "shop", "")
if row.Pods != 2 {
t.Errorf("pods = %d, want both counted", row.Pods)
}
if row.CPURequested != 400 {
t.Errorf("cpu requested = %d, want only the pod that asked for something", row.CPURequested)
}
if row.CPUReclaimable != 300 {
t.Errorf("cpu reclaimable = %d, want the pod that reserves nothing to add nothing", row.CPUReclaimable)
}
if !strings.Contains(report.Reason, "1 pod reserves nothing") {
t.Errorf("reason = %q, want it to count the pod that reserves nothing", report.Reason)
}
if !strings.Contains(report.Reason, "shop/loose") {
t.Errorf("reason = %q, want it to name the pod that reserves nothing", report.Reason)
}
}
func TestReclaimableFloorsAtZeroWhenAPodUsesMoreThanItReserved(t *testing.T) {
cluster := &fakeCluster{pods: []*unstructured.Unstructured{
podOf("shop", "web-1", requesting("100m", "128Mi")),
}}
usage := map[string]api.ResourceUsage{"shop/web-1": {CPUMilli: 400, MemoryMi: 512}}
report := built(t, cluster, reads(usage, "metrics-server", "right now"))
row := rowFor(t, report.Namespaces, "shop", "")
if row.CPUReclaimable != 0 || row.MemReclaimable != 0 {
t.Errorf("reclaimable = %d cpu, %d memory, want both floored at zero", row.CPUReclaimable, row.MemReclaimable)
}
if row.CPUUsed != 400 || row.MemUsed != 512 {
t.Errorf("used = %d cpu, %d memory, want what was measured", row.CPUUsed, row.MemUsed)
}
}
func TestTheRollupsHoldTwoWorkloadsInOneNamespaceAndABarePod(t *testing.T) {
cluster := &fakeCluster{
pods: []*unstructured.Unstructured{
ownedBy(podOf("shop", "web-1", requesting("100m", "128Mi")), "apps/v1", "ReplicaSet", "web-abc"),
ownedBy(podOf("shop", "web-2", requesting("100m", "128Mi")), "apps/v1", "ReplicaSet", "web-abc"),
ownedBy(podOf("shop", "api-1", requesting("200m", "256Mi")), "apps/v1", "ReplicaSet", "api-xyz"),
podOf("shop", "loose", requesting("50m", "32Mi")),
},
replicaSets: []*unstructured.Unstructured{
replicaSetOf("shop", "web-abc", "web"),
replicaSetOf("shop", "api-xyz", "api"),
},
}
report := built(t, cluster)
if len(report.Namespaces) != 1 {
t.Fatalf("namespaces = %+v, want one row", report.Namespaces)
}
if report.Namespaces[0].Pods != 4 {
t.Errorf("namespace pods = %d, want every pod counted once", report.Namespaces[0].Pods)
}
for _, want := range []struct {
kind string
name string
pods int
cpu int64
}{
{"Deployment", "web", 2, 200},
{"Deployment", "api", 1, 200},
{"Pod", "loose", 1, 50},
} {
row := rowFor(t, report.Workloads, "shop", want.name)
if row.Kind != want.kind {
t.Errorf("%s kind = %q, want %q", want.name, row.Kind, want.kind)
}
if row.Pods != want.pods {
t.Errorf("%s pods = %d, want %d", want.name, row.Pods, want.pods)
}
if row.CPURequested != want.cpu {
t.Errorf("%s cpu requested = %d, want %d", want.name, row.CPURequested, want.cpu)
}
}
}
func TestWithNoUsageSourceTheReportSaysSoAndStillShowsWhatIsReserved(t *testing.T) {
cluster := &fakeCluster{pods: []*unstructured.Unstructured{
podOf("shop", "web-1", requesting("400m", "512Mi")),
}}
report := built(t, cluster)
if report.Measured {
t.Error("measured = true, want the report to say nothing measured usage")
}
if !strings.Contains(report.Reason, notMeasured) {
t.Errorf("reason = %q, want it to say nothing measured usage", report.Reason)
}
if !strings.Contains(report.Reason, noSource) {
t.Errorf("reason = %q, want it to say why", report.Reason)
}
if report.Window != "" || report.Source != "" {
t.Errorf("window = %q, source = %q, want neither claimed", report.Window, report.Source)
}
if len(report.PartialOn) != 0 {
t.Errorf("partialOn = %v, want the reason to carry it rather than every row", report.PartialOn)
}
row := rowFor(t, report.Namespaces, "shop", "")
if row.CPURequested != 400 || row.MemRequested != 512 {
t.Errorf("row = %+v, want the reserved columns still filled", row)
}
if row.Measured {
t.Error("row measured = true, want a row nothing measured to say so")
}
if row.CPUUsed != 0 || row.CPUReclaimable != 0 {
t.Errorf("row = %+v, want no fabricated usage", row)
}
}
func TestAWindowIsPreferredOverTheLiveReadingWhenBothAnswer(t *testing.T) {
cluster := &fakeCluster{pods: []*unstructured.Unstructured{
podOf("shop", "web-1", requesting("400m", "512Mi")),
}}
over := reads(map[string]api.ResourceUsage{"shop/web-1": {CPUMilli: 100}}, "prometheus", "the last 30 minutes")
now := reads(map[string]api.ResourceUsage{"shop/web-1": {CPUMilli: 380}}, "metrics-server", "right now")
report := built(t, cluster, over, now)
if report.Source != "prometheus" || report.Window != "the last 30 minutes" {
t.Errorf("source = %q, window = %q, want the window reading", report.Source, report.Window)
}
if now.calls != 0 {
t.Errorf("metrics-server calls = %d, want it left alone while prometheus answers", now.calls)
}
row := rowFor(t, report.Namespaces, "shop", "")
if row.CPUUsed != 100 {
t.Errorf("cpu used = %d, want the window reading", row.CPUUsed)
}
}
func TestTheLiveReadingIsUsedWhenTheWindowFails(t *testing.T) {
cluster := &fakeCluster{pods: []*unstructured.Unstructured{
podOf("shop", "web-1", requesting("400m", "512Mi")),
}}
over := &fakeMeter{err: errors.New("prometheus is unavailable")}
now := reads(map[string]api.ResourceUsage{"shop/web-1": {CPUMilli: 380}}, "metrics-server", "right now")
report := built(t, cluster, over, now)
if !report.Measured {
t.Fatal("measured = false, want the fallback to count")
}
if report.Source != "metrics-server" || report.Window != "right now" {
t.Errorf("source = %q, window = %q, want the live reading", report.Source, report.Window)
}
row := rowFor(t, report.Namespaces, "shop", "")
if row.CPUUsed != 380 {
t.Errorf("cpu used = %d, want the live reading", row.CPUUsed)
}
}
func TestANamespaceWhoseUsageCouldNotBeReadIsNamedRatherThanCountedAsZero(t *testing.T) {
cluster := &fakeCluster{pods: []*unstructured.Unstructured{
podOf("shop", "web-1", requesting("400m", "512Mi")),
podOf("quiet", "job-1", requesting("800m", "1024Mi")),
}}
usage := map[string]api.ResourceUsage{"shop/web-1": {CPUMilli: 100, MemoryMi: 128}}
report := built(t, cluster, reads(usage, "metrics-server", "right now"))
if len(report.PartialOn) != 1 || report.PartialOn[0] != "quiet" {
t.Fatalf("partialOn = %v, want the namespace nothing measured", report.PartialOn)
}
quiet := rowFor(t, report.Namespaces, "quiet", "")
if quiet.Measured {
t.Error("quiet measured = true, want a namespace nothing measured to say so")
}
if quiet.CPURequested != 800 {
t.Errorf("quiet cpu requested = %d, want what it reserves", quiet.CPURequested)
}
if quiet.CPUReclaimable != 0 || quiet.MemReclaimable != 0 {
t.Errorf("quiet = %+v, want no reclaimable invented from unread usage", quiet)
}
shop := rowFor(t, report.Namespaces, "shop", "")
if !shop.Measured {
t.Error("shop measured = false, want the namespace that was read to say so")
}
}
func TestAWorkloadWhoseUsageCouldNotBeReadIsNamedWhenItsNeighbourWasRead(t *testing.T) {
cluster := &fakeCluster{pods: []*unstructured.Unstructured{
ownedBy(podOf("shop", "web-1", requesting("400m", "512Mi")), "apps/v1", "ReplicaSet", "web-abc"),
ownedBy(podOf("shop", "api-1", requesting("400m", "512Mi")), "apps/v1", "ReplicaSet", "api-xyz"),
}}
usage := map[string]api.ResourceUsage{"shop/web-1": {CPUMilli: 100, MemoryMi: 128}}
report := built(t, cluster, reads(usage, "metrics-server", "right now"))
if len(report.PartialOn) != 1 || report.PartialOn[0] != "shop/api-xyz" {
t.Fatalf("partialOn = %v, want the workload nothing measured", report.PartialOn)
}
}
func TestTheRowsAreOrderedByTheirCombinedShareOfWhatIsReclaimable(t *testing.T) {
cluster := &fakeCluster{pods: []*unstructured.Unstructured{
podOf("cpuheavy", "one", requesting("1000m", "100Mi")),
podOf("memheavy", "one", requesting("200m", "4000Mi")),
}}
usage := map[string]api.ResourceUsage{"cpuheavy/one": {}, "memheavy/one": {}}
report := built(t, cluster, reads(usage, "metrics-server", "right now"))
if len(report.Namespaces) != 2 {
t.Fatalf("namespaces = %+v, want two rows", report.Namespaces)
}
if report.Namespaces[0].Namespace != "memheavy" {
t.Errorf(
"first row = %q, want the namespace whose combined share is larger and not the one with more cpu",
report.Namespaces[0].Namespace,
)
}
if report.Namespaces[1].Namespace != "cpuheavy" {
t.Errorf("second row = %q, want cpuheavy", report.Namespaces[1].Namespace)
}
}
func TestAWorkloadIsNamedByTheOwnerAtTheTopOfTheChain(t *testing.T) {
cluster := &fakeCluster{
pods: []*unstructured.Unstructured{
ownedBy(podOf("shop", "web-1", requesting("100m", "128Mi")), "apps/v1", "ReplicaSet", "web-abc"),
},
replicaSets: []*unstructured.Unstructured{replicaSetOf("shop", "web-abc", "web")},
}
report := built(t, cluster)
row := rowFor(t, report.Workloads, "shop", "web")
if row.Kind != "Deployment" {
t.Errorf("kind = %q, want the top of the owner chain", row.Kind)
}
}
func TestAWorkloadListThatCouldNotBeReadIsSaidSoAndTheOwnerIsStillNamed(t *testing.T) {
cluster := &fakeCluster{
pods: []*unstructured.Unstructured{
ownedBy(podOf("shop", "web-1", requesting("100m", "128Mi")), "apps/v1", "ReplicaSet", "web-abc"),
},
ownerErr: errors.New("replicasets are forbidden here"),
}
report := built(t, cluster)
row := rowFor(t, report.Workloads, "shop", "web-abc")
if row.Kind != "ReplicaSet" {
t.Errorf("kind = %q, want the owner the pod itself names", row.Kind)
}
if !strings.Contains(report.Reason, "replicasets could not be read") {
t.Errorf("reason = %q, want it to say the owner chain was not walked", report.Reason)
}
}
func TestAFailingPodListIsAnErrorRatherThanAnEmptyReport(t *testing.T) {
cluster := &fakeCluster{podErr: errors.New("pods are forbidden here")}
_, err := Build(t.Context(), cluster, Request{})
if err == nil {
t.Fatal("err = nil, want a failed pod list reported rather than an empty table")
}
}
func TestAClusterWithNoPodsSaysSoRatherThanShowingAnEmptyTable(t *testing.T) {
report := built(t, &fakeCluster{})
if report.Reason != noPods {
t.Errorf("reason = %q, want %q", report.Reason, noPods)
}
if report.Measured {
t.Error("measured = true, want nothing claimed about usage")
}
}
func TestOnlyTheNamespaceTheRequestNamesIsRead(t *testing.T) {
cluster := &fakeCluster{pods: []*unstructured.Unstructured{
podOf("shop", "web-1", requesting("100m", "128Mi")),
}}
_, err := Build(t.Context(), cluster, Request{Namespace: "shop"})
if err != nil {
t.Fatalf("build: %v", err)
}
if len(cluster.asked) == 0 {
t.Fatal("nothing was listed")
}
for _, ref := range cluster.asked {
if ref.Namespace != "shop" {
t.Errorf("listed %s in %q, want the namespace the request names", ref.Resource, ref.Namespace)
}
}
}
func TestALongListOfPodsThatReserveNothingSaysHowManyItDidNotName(t *testing.T) {
cluster := &fakeCluster{pods: []*unstructured.Unstructured{
podOf("shop", "one", bareContainer()),
podOf("shop", "two", bareContainer()),
podOf("shop", "three", bareContainer()),
podOf("shop", "four", bareContainer()),
podOf("shop", "five", bareContainer()),
}}
report := built(t, cluster)
if !strings.Contains(report.Reason, "5 pods reserve nothing") {
t.Errorf("reason = %q, want the whole count", report.Reason)
}
if !strings.Contains(report.Reason, "and 2 more") {
t.Errorf("reason = %q, want the cut list to say how many it did not name", report.Reason)
}
}
func TestARequestIsReadWhateverShapeTheSpecHoldsIt(t *testing.T) {
for _, one := range []struct {
name string
cpu any
memory any
want int64
asks bool
}{
{name: "a quantity string", cpu: "250m", memory: "128Mi", want: 250, asks: true},
{name: "a whole number of cores", cpu: int64(2), memory: int64(1024 * 1024), want: 2000, asks: true},
{name: "a fraction of a core", cpu: 0.5, memory: float64(1024 * 1024), want: 500, asks: true},
{name: "something that is not a quantity", cpu: "half a core", memory: true, want: 0, asks: false},
{name: "nothing at all", cpu: nil, memory: nil, want: 0, asks: false},
} {
t.Run(one.name, func(t *testing.T) {
asked := map[string]any{}
if one.cpu != nil {
asked["cpu"] = one.cpu
}
if one.memory != nil {
asked["memory"] = one.memory
}
pod := podOf("shop", "web-1", map[string]any{
"resources": map[string]any{"requests": asked},
})
cpu, _, asks := requestsOf(pod)
if cpu != one.want {
t.Errorf("cpu = %d, want %d", cpu, one.want)
}
if asks != one.asks {
t.Errorf("asks = %t, want %t", asks, one.asks)
}
})
}
}
func TestAContainerWithNoResourcesBlockReservesNothing(t *testing.T) {
for _, one := range []struct {
name string
container map[string]any
}{
{name: "no resources key", container: map[string]any{"image": "nginx"}},
{name: "no requests key", container: map[string]any{"resources": map[string]any{"limits": map[string]any{"cpu": "1"}}}},
{name: "not a container at all", container: nil},
} {
t.Run(one.name, func(t *testing.T) {
pod := podOf("shop", "web-1")
pod.Object["spec"] = map[string]any{"containers": []any{one.container}}
cpu, mem, asks := requestsOf(pod)
if cpu != 0 || mem != 0 || asks {
t.Errorf("requests = %d cpu, %d memory, asks %t, want nothing reserved", cpu, mem, asks)
}
})
}
}
func TestAnOwnerReferenceIsWalkedOnlyWhenItControlsThePod(t *testing.T) {
loose := podOf("shop", "loose", requesting("100m", "128Mi"))
loose.SetOwnerReferences([]metav1.OwnerReference{{APIVersion: "v1", Kind: "Node", Name: "node-1"}})
core := ownedBy(podOf("shop", "core-1", requesting("100m", "128Mi")), "v1", "ReplicationController", "core")
cluster := &fakeCluster{pods: []*unstructured.Unstructured{loose, core}}
report := built(t, cluster)
bare := rowFor(t, report.Workloads, "shop", "loose")
if bare.Kind != "Pod" {
t.Errorf("kind = %q, want a pod nothing controls to stand as its own workload", bare.Kind)
}
owned := rowFor(t, report.Workloads, "shop", "core")
if owned.Kind != "ReplicationController" {
t.Errorf("kind = %q, want the controller in the core group", owned.Kind)
}
}
func TestAnOwnerChainStopsAtTheDepthItIsAllowed(t *testing.T) {
pod := ownedBy(podOf("shop", "web-1", requesting("100m", "128Mi")), "apps/v1", "ReplicaSet", "step-0")
sets := make([]*unstructured.Unstructured, 0, ownerDepth+2)
for at := range ownerDepth + 2 {
set := replicaSetOf("shop", "step-"+strconv.Itoa(at), "")
ownedBy(set, "apps/v1", "ReplicaSet", "step-"+strconv.Itoa(at+1))
sets = append(sets, set)
}
cluster := &fakeCluster{pods: []*unstructured.Unstructured{pod}, replicaSets: sets}
report := built(t, cluster)
if len(report.Workloads) != 1 {
t.Fatalf("workloads = %+v, want one row", report.Workloads)
}
if report.Workloads[0].Name != "step-"+strconv.Itoa(ownerDepth-1) {
t.Errorf("workload = %q, want the walk stopped at the depth it is allowed", report.Workloads[0].Name)
}
}
func TestEverySourceFailingSaysWhatEachOneSaid(t *testing.T) {
cluster := &fakeCluster{pods: []*unstructured.Unstructured{
podOf("shop", "web-1", requesting("400m", "512Mi")),
}}
over := &fakeMeter{err: errors.New("prometheus is unavailable")}
now := &fakeMeter{err: errors.New("metrics-server did not answer")}
report := built(t, cluster, over, now)
if report.Measured {
t.Error("measured = true, want the report to say nothing measured usage")
}
for _, want := range []string{"prometheus is unavailable", "metrics-server did not answer"} {
if !strings.Contains(report.Reason, want) {
t.Errorf("reason = %q, want it to hold %q", report.Reason, want)
}
}
}
func TestRowsWithNothingReclaimableAreStillOrderedTheSameWayTwice(t *testing.T) {
cluster := &fakeCluster{pods: []*unstructured.Unstructured{
podOf("beta", "one", bareContainer()),
podOf("alpha", "one", bareContainer()),
}}
usage := map[string]api.ResourceUsage{"beta/one": {}, "alpha/one": {}}
report := built(t, cluster, reads(usage, "metrics-server", "right now"))
if report.Namespaces[0].Namespace != "alpha" || report.Namespaces[1].Namespace != "beta" {
t.Errorf("namespaces = %+v, want a stable order by name when nothing is reclaimable", report.Namespaces)
}
}
func TestAFinishedPodReservesNothingAndIsLeftOut(t *testing.T) {
cases := []struct {
name string
phase string
kept bool
}{
{name: "a running pod", phase: "Running", kept: true},
{name: "a pending pod", phase: "Pending", kept: true},
{name: "a pod with no phase yet", phase: "", kept: true},
{name: "a job pod that finished", phase: "Succeeded"},
{name: "a pod that failed", phase: "Failed"},
}
for _, one := range cases {
t.Run(one.name, func(t *testing.T) {
pod := podOf("shop", "worker", requesting("500m", "512Mi"))
if one.phase != "" {
if err := unstructured.SetNestedField(pod.Object, one.phase, "status", "phase"); err != nil {
t.Fatalf("set phase: %v", err)
}
}
got := reservations([]*unstructured.Unstructured{pod}, nil)
if one.kept && len(got) != 1 {
t.Fatalf("a %s pod was left out", one.phase)
}
if !one.kept && len(got) != 0 {
t.Fatalf("a %s pod was counted as reserving something", one.phase)
}
})
}
}