Checker is an optional interface next to Provider: Check returns one verdict per target and never fails as a whole. The kubernetes provider lists at most one managed Pod cluster-wide (the verb and scope GC relies on); the GCP provider fetches a single page of at most one managed instance through a new Probe seam method, since the SDK treats MaxResults as a page size and the existing AggregatedList drains every page. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
115 lines
3.8 KiB
Go
115 lines
3.8 KiB
Go
package kubernetes
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/url"
|
|
"syscall"
|
|
"testing"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
|
"sigs.k8s.io/controller-runtime/pkg/client/interceptor"
|
|
|
|
"gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/internal/provider"
|
|
)
|
|
|
|
type listFunc func(ctx context.Context, c client.WithWatch, list client.ObjectList, opts ...client.ListOption) error
|
|
|
|
func newCheckProvider(list listFunc) *Provider {
|
|
scheme := runtime.NewScheme()
|
|
if err := corev1.AddToScheme(scheme); err != nil {
|
|
panic(err)
|
|
}
|
|
c := fake.NewClientBuilder().WithScheme(scheme).WithInterceptorFuncs(interceptor.Funcs{List: list}).Build()
|
|
p := newWithClient(c, provider.ProviderConfig{Name: "kubernetes"})
|
|
p.apiHost = "https://10.96.0.1:443"
|
|
return p
|
|
}
|
|
|
|
func TestCheck_reachable(t *testing.T) {
|
|
t.Parallel()
|
|
p := newTestProvider(testPod("ns", "a", "uid-a", corev1.PodRunning, "10.0.0.1"))
|
|
p.apiHost = "https://10.96.0.1:443"
|
|
|
|
got := p.Check(context.Background())
|
|
|
|
if len(got) != 1 {
|
|
t.Fatalf("Check() returned %d targets, want 1", len(got))
|
|
}
|
|
ts := got[0]
|
|
if !ts.Ready || ts.Reason != provider.ReasonReachable || ts.Message != "" {
|
|
t.Errorf("Check() = %+v, want ready and Reachable", ts)
|
|
}
|
|
if ts.Name != "apiserver" || ts.Address != "https://10.96.0.1:443" {
|
|
t.Errorf("target = %q at %q, want apiserver at https://10.96.0.1:443", ts.Name, ts.Address)
|
|
}
|
|
}
|
|
|
|
func TestCheck_listOptions(t *testing.T) {
|
|
t.Parallel()
|
|
var got client.ListOptions
|
|
p := newCheckProvider(func(ctx context.Context, c client.WithWatch, list client.ObjectList, opts ...client.ListOption) error {
|
|
got.ApplyOptions(opts)
|
|
return c.List(ctx, list, opts...)
|
|
})
|
|
|
|
p.Check(context.Background())
|
|
|
|
if got.Limit != 1 {
|
|
t.Errorf("Limit = %d, want 1", got.Limit)
|
|
}
|
|
if got.Namespace != "" {
|
|
t.Errorf("Namespace = %q, want cluster-wide", got.Namespace)
|
|
}
|
|
want := provider.LabelManaged + "=" + provider.LabelManagedYes
|
|
if got.LabelSelector == nil || got.LabelSelector.String() != want {
|
|
t.Errorf("LabelSelector = %v, want %s", got.LabelSelector, want)
|
|
}
|
|
}
|
|
|
|
func TestCheck_failureReasons(t *testing.T) {
|
|
t.Parallel()
|
|
pods := schema.GroupResource{Resource: "pods"}
|
|
ended, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
ctx context.Context
|
|
err error
|
|
want string
|
|
}{
|
|
{"forbidden", context.Background(), apierrors.NewForbidden(pods, "", errors.New("rbac denied")), provider.ReasonForbidden},
|
|
{"unauthorized", context.Background(), apierrors.NewUnauthorized("token expired"), provider.ReasonUnauthorized},
|
|
{"api timeout", context.Background(), apierrors.NewTimeoutError("slow etcd", 1), provider.ReasonTimeout},
|
|
{"server timeout", context.Background(), apierrors.NewServerTimeout(pods, "list", 1), provider.ReasonTimeout},
|
|
{"context ended", ended, context.Canceled, provider.ReasonTimeout},
|
|
{"internal error", context.Background(), apierrors.NewInternalError(errors.New("etcd down")), provider.ReasonAPIError},
|
|
{"connection refused", context.Background(), &url.Error{
|
|
Op: "Get", URL: "https://10.96.0.1:443/api/v1/pods", Err: syscall.ECONNREFUSED,
|
|
}, provider.ReasonUnreachable},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
p := newCheckProvider(func(context.Context, client.WithWatch, client.ObjectList, ...client.ListOption) error {
|
|
return tc.err
|
|
})
|
|
|
|
got := p.Check(tc.ctx)
|
|
|
|
if len(got) != 1 {
|
|
t.Fatalf("Check() returned %d targets, want 1", len(got))
|
|
}
|
|
if got[0].Ready || got[0].Reason != tc.want || got[0].Message != tc.err.Error() {
|
|
t.Errorf("Check() = %+v, want not ready, reason %s, message %q", got[0], tc.want, tc.err.Error())
|
|
}
|
|
})
|
|
}
|
|
}
|