Add the provider Checker contract and the kubernetes and GCP checks
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>
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user