Ctrl K
ComunidadGuías…

internal/prom/proxy_failures_test.go

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

Archivo original ↗Preguntar a KiBot
package prom

import (
	"errors"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/client-go/kubernetes"
	k8sfake "k8s.io/client-go/kubernetes/fake"
	"k8s.io/client-go/rest"
)

type proxyRoundTripper func(*http.Request) (*http.Response, error)

func (roundTrip proxyRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
	return roundTrip(request)
}

type trackedProxyBody struct {
	reader io.Reader
	closed bool
}

func (body *trackedProxyBody) Read(buffer []byte) (int, error) {
	return body.reader.Read(buffer)
}

func (body *trackedProxyBody) Close() error {
	body.closed = true
	return nil
}

func proxyClientset(t *testing.T, roundTrip http.RoundTripper) kubernetes.Interface {
	t.Helper()
	clientset, err := kubernetes.NewForConfigAndClient(
		&rest.Config{Host: "https://apiserver.invalid"},
		&http.Client{Transport: roundTrip},
	)
	if err != nil {
		t.Fatalf("clientset: %v", err)
	}
	return clientset
}

func proxyTarget() Target {
	return Target{Namespace: "monitoring", Service: "prometheus", Port: "9090", Scheme: "https"}
}

func TestServiceProxyRefusesAClientWithoutARESTTransport(t *testing.T) {
	proxy := &serviceProxy{cs: k8sfake.NewClientset()}

	_, err := proxy.Get(t.Context(), proxyTarget(), rangePath, nil)

	if err == nil {
		t.Fatal("a client with no REST transport was allowed to proxy prometheus")
	}
	if !strings.Contains(err.Error(), "cannot safely proxy") {
		t.Fatalf("error = %q, want the unavailable transport named", err)
	}
}

func TestServiceProxySurfacesATransportFailure(t *testing.T) {
	failure := errors.New("apiserver connection reset")
	clientset := proxyClientset(t, proxyRoundTripper(func(request *http.Request) (*http.Response, error) {
		if request.Header.Get("Accept") != "application/json" {
			t.Errorf("accept = %q, want application/json", request.Header.Get("Accept"))
		}
		if _, bounded := request.Context().Deadline(); !bounded {
			t.Error("prometheus proxy request has no deadline")
		}
		return nil, failure
	}))
	proxy := &serviceProxy{cs: clientset}

	_, err := proxy.Get(t.Context(), proxyTarget(), rangePath, nil)

	if !errors.Is(err, failure) {
		t.Fatalf("error = %v, want the transport failure", err)
	}
}

func TestServiceProxyFallsBackToTheDefaultHTTPClient(t *testing.T) {
	apiserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		_, _ = w.Write([]byte("healthy"))
	}))
	t.Cleanup(apiserver.Close)
	clientset, err := kubernetes.NewForConfig(&rest.Config{Host: apiserver.URL})
	if err != nil {
		t.Fatalf("clientset: %v", err)
	}
	restClient, ok := clientset.CoreV1().RESTClient().(*rest.RESTClient)
	if !ok {
		t.Fatalf("REST client = %T", clientset.CoreV1().RESTClient())
	}
	restClient.Client = nil
	proxy := &serviceProxy{cs: clientset}

	body, err := proxy.Get(t.Context(), proxyTarget(), rangePath, nil)
	if err != nil {
		t.Fatalf("proxy with the default HTTP client: %v", err)
	}
	if string(body) != "healthy" {
		t.Fatalf("body = %q, want the proxied response", body)
	}
}

func TestServiceProxyClosesAResponseThatFailsWhileReading(t *testing.T) {
	failure := errors.New("response stream reset")
	body := &trackedProxyBody{reader: io.MultiReader(strings.NewReader("partial"), errorReader{err: failure})}
	clientset := proxyClientset(t, proxyRoundTripper(func(request *http.Request) (*http.Response, error) {
		return &http.Response{
			StatusCode: http.StatusOK,
			Body:       body,
			Request:    request,
		}, nil
	}))
	proxy := &serviceProxy{cs: clientset}

	_, err := proxy.Get(t.Context(), proxyTarget(), rangePath, nil)

	if !errors.Is(err, failure) {
		t.Fatalf("error = %v, want the response read failure", err)
	}
	if !body.closed {
		t.Fatal("the failed response body was left open")
	}
}

func TestServiceProxyPreservesAnAPIServerRejection(t *testing.T) {
	body := &trackedProxyBody{reader: strings.NewReader("services/proxy is forbidden")}
	clientset := proxyClientset(t, proxyRoundTripper(func(request *http.Request) (*http.Response, error) {
		return &http.Response{
			StatusCode: http.StatusForbidden,
			Body:       body,
			Request:    request,
		}, nil
	}))
	proxy := &serviceProxy{cs: clientset}

	_, err := proxy.Get(t.Context(), proxyTarget(), rangePath, nil)

	if !apierrors.IsForbidden(err) {
		t.Fatalf("error = %v, want a Kubernetes forbidden error", err)
	}
	if !strings.Contains(err.Error(), "services/proxy is forbidden") {
		t.Fatalf("error = %q, want the apiserver response", err)
	}
	if !body.closed {
		t.Fatal("the rejected response body was left open")
	}
}

func TestServiceProxyAcceptsAResponseAtTheSizeLimit(t *testing.T) {
	body := &trackedProxyBody{reader: io.LimitReader(zeroReader{}, maxResponseBytes)}
	clientset := proxyClientset(t, proxyRoundTripper(func(request *http.Request) (*http.Response, error) {
		return &http.Response{
			StatusCode: http.StatusOK,
			Body:       body,
			Request:    request,
		}, nil
	}))
	proxy := &serviceProxy{cs: clientset}

	response, err := proxy.Get(t.Context(), proxyTarget(), rangePath, nil)
	if err != nil {
		t.Fatalf("response at the size limit: %v", err)
	}
	if len(response) != maxResponseBytes {
		t.Fatalf("response bytes = %d, want %d", len(response), maxResponseBytes)
	}
	if !body.closed {
		t.Fatal("the accepted response body was left open")
	}
}

type errorReader struct {
	err error
}

func (reader errorReader) Read([]byte) (int, error) {
	return 0, reader.err
}

type zeroReader struct{}

func (zeroReader) Read(buffer []byte) (int, error) {
	clear(buffer)
	return len(buffer), nil
}