Ctrl K
ComunidadGuías…

internal/waste/meters_test.go

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

Archivo original ↗Preguntar a KiBot
package waste

import (
	"context"
	"errors"
	"strings"
	"sync"
	"testing"
	"time"

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

type fakeSampler struct {
	metrics api.Metrics
}

func (fs fakeSampler) Metrics(context.Context) api.Metrics {
	return fs.metrics
}

type fakeHistorian struct {
	mu    sync.Mutex
	byPod map[string]api.MetricHistory
	err   error
	spans []time.Duration
}

func (fh *fakeHistorian) asked() []time.Duration {
	fh.mu.Lock()
	defer fh.mu.Unlock()
	return append([]time.Duration{}, fh.spans...)
}

func (fh *fakeHistorian) MetricHistory(
	_ context.Context,
	namespace, pod string,
	span time.Duration,
) (api.MetricHistory, error) {
	fh.mu.Lock()
	fh.spans = append(fh.spans, span)
	fh.mu.Unlock()
	if fh.err != nil {
		return api.MetricHistory{}, fh.err
	}
	history, ok := fh.byPod[namespace+"/"+pod]
	if !ok {
		return api.MetricHistory{}, errors.New("no history for " + namespace + "/" + pod)
	}
	return history, nil
}

func pointsOf(values ...float64) []api.MetricPoint {
	out := make([]api.MetricPoint, 0, len(values))
	for at, value := range values {
		out = append(out, api.MetricPoint{At: int64(at), Value: value})
	}
	return out
}

func TestTheLiveMeterNamesMetricsServerAndTheMomentItRead(t *testing.T) {
	meter := RightNow(fakeSampler{metrics: api.Metrics{Pods: map[string]api.ResourceUsage{
		"shop/web-1": {CPUMilli: 120, MemoryMi: 256},
	}}})

	reading, err := meter.Usage(t.Context(), []Ref{{Namespace: "shop", Name: "web-1"}})
	if err != nil {
		t.Fatalf("usage: %v", err)
	}

	if reading.Source != liveSource || reading.Window != liveWindow {
		t.Errorf("source = %q, window = %q, want the live reading named", reading.Source, reading.Window)
	}
	if reading.Pods["shop/web-1"].CPUMilli != 120 {
		t.Errorf("usage = %+v, want what metrics-server said", reading.Pods["shop/web-1"])
	}
}

func TestTheLiveMeterRefusesRatherThanAnswerFromAFailedRead(t *testing.T) {
	for _, one := range []struct {
		name    string
		metrics api.Metrics
		want    string
	}{
		{
			name:    "metrics-server reported an error",
			metrics: api.Metrics{Error: "pods.metrics.k8s.io: the server could not find the requested resource"},
			want:    "could not find",
		},
		{
			name:    "metrics-server knew none of the pods",
			metrics: api.Metrics{Pods: map[string]api.ResourceUsage{"other/thing": {CPUMilli: 1}}},
			want:    "named none of the pods",
		},
	} {
		t.Run(one.name, func(t *testing.T) {
			meter := RightNow(fakeSampler{metrics: one.metrics})

			_, err := meter.Usage(t.Context(), []Ref{{Namespace: "shop", Name: "web-1"}})

			if err == nil {
				t.Fatal("err = nil, want the read refused so another source is tried")
			}
			if !strings.Contains(err.Error(), one.want) {
				t.Errorf("err = %q, want it to contain %q", err, one.want)
			}
		})
	}
}

func TestTheWindowMeterAveragesTheHistoryOfEachPod(t *testing.T) {
	historian := &fakeHistorian{byPod: map[string]api.MetricHistory{
		"shop/web-1": {
			Source: "monitoring/prometheus:9090 (https)",
			CPU:    pointsOf(0.1, 0.3),
			Memory: pointsOf(100*bytesPerMi, 300*bytesPerMi),
		},
	}}
	meter := OverAWindow(historian, 30*time.Minute)

	reading, err := meter.Usage(t.Context(), []Ref{{Namespace: "shop", Name: "web-1"}})
	if err != nil {
		t.Fatalf("usage: %v", err)
	}

	use := reading.Pods["shop/web-1"]
	if use.CPUMilli != 200 {
		t.Errorf("cpu = %d millicores, want the mean of the window in millicores", use.CPUMilli)
	}
	if use.MemoryMi != 200 {
		t.Errorf("memory = %d mebibytes, want the mean of the window in mebibytes", use.MemoryMi)
	}
	if reading.Window != "the last 30 minutes" {
		t.Errorf("window = %q, want it said in words", reading.Window)
	}
	if reading.Source != "monitoring/prometheus:9090 (https)" {
		t.Errorf("source = %q, want where the history came from", reading.Source)
	}
	asked := historian.asked()
	if len(asked) != 1 || asked[0] != 30*time.Minute {
		t.Errorf("spans = %v, want the span it was built with", asked)
	}
}

func TestTheWindowMeterNamesWhereTheHistoryCameFrom(t *testing.T) {
	for _, one := range []struct {
		name    string
		history api.MetricHistory
		want    string
	}{
		{name: "spinoza recorded it", history: api.MetricHistory{Sampled: true}, want: sampledSource},
		{name: "prometheus named a target", history: api.MetricHistory{Source: "monitoring/prom:9090"}, want: "monitoring/prom:9090"},
		{name: "nothing named itself", history: api.MetricHistory{}, want: promSource},
	} {
		t.Run(one.name, func(t *testing.T) {
			one.history.CPU = pointsOf(0.5)

			historian := &fakeHistorian{byPod: map[string]api.MetricHistory{"shop/web-1": one.history}}
			reading, err := OverAWindow(historian, time.Hour).Usage(t.Context(), []Ref{{Namespace: "shop", Name: "web-1"}})
			if err != nil {
				t.Fatalf("usage: %v", err)
			}

			if reading.Source != one.want {
				t.Errorf("source = %q, want %q", reading.Source, one.want)
			}
		})
	}
}

func TestTheWindowMeterDeclinesMorePodsThanItWillRead(t *testing.T) {
	historian := &fakeHistorian{byPod: map[string]api.MetricHistory{}}
	pods := make([]Ref, 0, windowPodBudget+1)
	for at := range windowPodBudget + 1 {
		pods = append(pods, Ref{Namespace: "shop", Name: "web-" + string(rune('a'+at%26))})
	}

	_, err := OverAWindow(historian, time.Hour).Usage(t.Context(), pods)

	if err == nil {
		t.Fatal("err = nil, want the window to decline rather than read one pod at a time")
	}
	if reads := len(historian.asked()); reads != 0 {
		t.Errorf("reads = %d, want none attempted", reads)
	}
	if !strings.Contains(err.Error(), "ask about one namespace") {
		t.Errorf("err = %q, want it to say what to do instead", err)
	}
}

func TestAPodWithNoHistoryIsLeftUnmeasuredRatherThanReadAsZero(t *testing.T) {
	historian := &fakeHistorian{byPod: map[string]api.MetricHistory{
		"shop/web-1": {CPU: pointsOf(0.5)},
		"shop/web-2": {},
	}}

	reading, err := OverAWindow(historian, time.Hour).Usage(t.Context(), []Ref{
		{Namespace: "shop", Name: "web-1"},
		{Namespace: "shop", Name: "web-2"},
	})
	if err != nil {
		t.Fatalf("usage: %v", err)
	}

	if _, seen := reading.Pods["shop/web-2"]; seen {
		t.Errorf("pods = %+v, want the pod with no history left out rather than read as zero", reading.Pods)
	}
	if reading.Pods["shop/web-1"].CPUMilli != 500 {
		t.Errorf("web-1 = %+v, want the pod that had a history measured", reading.Pods["shop/web-1"])
	}
}

func TestAWindowNoPodAnsweredIsRefusedSoAnotherSourceIsTried(t *testing.T) {
	historian := &fakeHistorian{err: errors.New("prometheus is unavailable")}

	_, err := OverAWindow(historian, time.Hour).Usage(t.Context(), []Ref{{Namespace: "shop", Name: "web-1"}})

	if err == nil {
		t.Fatal("err = nil, want the window refused so another source is tried")
	}
	if !strings.Contains(err.Error(), "prometheus is unavailable") {
		t.Errorf("err = %q, want the reason prometheus gave", err)
	}
}

func TestTheWindowIsSaidInWords(t *testing.T) {
	for _, one := range []struct {
		span time.Duration
		want string
	}{
		{span: 0, want: "right now"},
		{span: 45 * time.Second, want: "the last 45 seconds"},
		{span: time.Minute, want: "the last minute"},
		{span: 30 * time.Minute, want: "the last 30 minutes"},
		{span: time.Hour, want: "the last hour"},
		{span: 6 * time.Hour, want: "the last 6 hours"},
		{span: 24 * time.Hour, want: "the last day"},
		{span: 72 * time.Hour, want: "the last 3 days"},
	} {
		t.Run(one.want, func(t *testing.T) {
			got := windowWords(one.span)
			if got != one.want {
				t.Errorf("windowWords(%s) = %q, want %q", one.span, got, one.want)
			}
		})
	}
}