551 lines
19 KiB
Go
551 lines
19 KiB
Go
package controller
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
ctrl "sigs.k8s.io/controller-runtime"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
|
|
|
crawlv1alpha1 "gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/api/v1alpha1"
|
|
"gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/internal/provider"
|
|
)
|
|
|
|
// Distinct per-interval values so an asserted ctrl.Result is unambiguous
|
|
// about which action-table row produced it.
|
|
const (
|
|
tProvisioningPoll = 11 * time.Second
|
|
tDriftPoll = 22 * time.Second
|
|
tDeletionPoll = 33 * time.Second
|
|
tQuotaRetry = 44 * time.Second
|
|
tRequeueNow = 55 * time.Millisecond
|
|
)
|
|
|
|
const (
|
|
testProxyName = "p1"
|
|
testNamespace = "default"
|
|
testUID = types.UID("11111111-2222-3333-4444-555555555555")
|
|
)
|
|
|
|
// stubProvider is the plan's in-test Provider stub: a handful of lines, no
|
|
// config format, no fault-injection surface beyond settable fields.
|
|
type stubProvider struct {
|
|
createID string
|
|
createErr error
|
|
getInst *provider.Instance
|
|
getErr error
|
|
deleteErr error
|
|
|
|
createCalls, getCalls, deleteCalls int
|
|
lastCreate provider.CreateRequest
|
|
}
|
|
|
|
func (s *stubProvider) Create(_ context.Context, req provider.CreateRequest) (string, error) {
|
|
s.createCalls++
|
|
s.lastCreate = req
|
|
if s.createErr != nil {
|
|
return "", s.createErr
|
|
}
|
|
return s.createID, nil
|
|
}
|
|
|
|
func (s *stubProvider) Get(_ context.Context, _ string) (*provider.Instance, error) {
|
|
s.getCalls++
|
|
if s.getErr != nil {
|
|
return nil, s.getErr
|
|
}
|
|
return s.getInst, nil
|
|
}
|
|
|
|
func (s *stubProvider) Delete(_ context.Context, _ string) error {
|
|
s.deleteCalls++
|
|
return s.deleteErr
|
|
}
|
|
|
|
func (s *stubProvider) ListByTag(context.Context) ([]provider.Instance, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func notFoundErr() error {
|
|
return provider.Wrap(provider.ErrNotFound, "get", "stub", "some-id", nil)
|
|
}
|
|
|
|
func testScheme(t *testing.T) *runtime.Scheme {
|
|
t.Helper()
|
|
s := runtime.NewScheme()
|
|
if err := crawlv1alpha1.AddToScheme(s); err != nil {
|
|
t.Fatalf("adding crawl scheme: %v", err)
|
|
}
|
|
if err := corev1.AddToScheme(s); err != nil {
|
|
t.Fatalf("adding core scheme: %v", err)
|
|
}
|
|
return s
|
|
}
|
|
|
|
// managedProxy returns a Managed proxy that already carries the finalizer —
|
|
// the state most action-table rows start from. Mutators adjust from there.
|
|
func managedProxy(mut ...func(*crawlv1alpha1.Proxy)) *crawlv1alpha1.Proxy {
|
|
p := &crawlv1alpha1.Proxy{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: testProxyName,
|
|
Namespace: testNamespace,
|
|
UID: testUID,
|
|
Generation: 1,
|
|
Finalizers: []string{crawlv1alpha1.FinalizerName},
|
|
},
|
|
Spec: crawlv1alpha1.ProxySpec{
|
|
Mode: crawlv1alpha1.ModeManaged,
|
|
Provider: "stub",
|
|
},
|
|
}
|
|
for _, m := range mut {
|
|
m(p)
|
|
}
|
|
return p
|
|
}
|
|
|
|
func withProviderID(id string) func(*crawlv1alpha1.Proxy) {
|
|
return func(p *crawlv1alpha1.Proxy) { p.Status.ProviderID = id }
|
|
}
|
|
|
|
func withSpecHashAnnotation(hash string) func(*crawlv1alpha1.Proxy) {
|
|
return func(p *crawlv1alpha1.Proxy) {
|
|
if p.Annotations == nil {
|
|
p.Annotations = map[string]string{}
|
|
}
|
|
p.Annotations[crawlv1alpha1.AnnotationSpecHash] = hash
|
|
}
|
|
}
|
|
|
|
func deleting() func(*crawlv1alpha1.Proxy) {
|
|
return func(p *crawlv1alpha1.Proxy) {
|
|
now := metav1.Now()
|
|
p.DeletionTimestamp = &now
|
|
}
|
|
}
|
|
|
|
func newTestReconciler(t *testing.T, stub *stubProvider, objs ...client.Object) *ProxyReconciler {
|
|
t.Helper()
|
|
c := fake.NewClientBuilder().
|
|
WithScheme(testScheme(t)).
|
|
WithStatusSubresource(&crawlv1alpha1.Proxy{}).
|
|
WithObjects(objs...).
|
|
Build()
|
|
return &ProxyReconciler{
|
|
Client: c,
|
|
Providers: map[string]provider.Provider{"stub": stub},
|
|
ProvisioningPoll: tProvisioningPoll,
|
|
DriftPoll: tDriftPoll,
|
|
DeletionPoll: tDeletionPoll,
|
|
QuotaRetry: tQuotaRetry,
|
|
RequeueNow: tRequeueNow,
|
|
}
|
|
}
|
|
|
|
func doReconcile(t *testing.T, r *ProxyReconciler) (ctrl.Result, error) {
|
|
t.Helper()
|
|
return r.Reconcile(context.Background(), ctrl.Request{
|
|
NamespacedName: types.NamespacedName{Namespace: testNamespace, Name: testProxyName},
|
|
})
|
|
}
|
|
|
|
func getProxy(t *testing.T, r *ProxyReconciler) *crawlv1alpha1.Proxy {
|
|
t.Helper()
|
|
var p crawlv1alpha1.Proxy
|
|
if err := r.Get(context.Background(), types.NamespacedName{Namespace: testNamespace, Name: testProxyName}, &p); err != nil {
|
|
t.Fatalf("getting proxy: %v", err)
|
|
}
|
|
return &p
|
|
}
|
|
|
|
func assertCondition(t *testing.T, p *crawlv1alpha1.Proxy, condType string, status metav1.ConditionStatus, reason string) {
|
|
t.Helper()
|
|
cond := apimeta.FindStatusCondition(p.Status.Conditions, condType)
|
|
if cond == nil {
|
|
t.Fatalf("condition %s missing, have %+v", condType, p.Status.Conditions)
|
|
}
|
|
if cond.Status != status || cond.Reason != reason {
|
|
t.Errorf("condition %s = %s/%s, want %s/%s", condType, cond.Status, cond.Reason, status, reason)
|
|
}
|
|
if cond.ObservedGeneration != p.Generation {
|
|
t.Errorf("condition %s observedGeneration = %d, want %d", condType, cond.ObservedGeneration, p.Generation)
|
|
}
|
|
}
|
|
|
|
// TestReconcile_actionTable exercises every row of the plan's action table
|
|
// by calling Reconcile directly against a fake client. Caveat (documented in
|
|
// the plan): the fake client runs neither CEL validation nor structural
|
|
// defaulting — the envtest suite covers those.
|
|
func TestReconcile_actionTable(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// The fixture's hash: no placement, no cloud-init, defaulted port.
|
|
freshHash := specHash(managedProxy(), "")
|
|
|
|
tests := []struct {
|
|
name string
|
|
proxy *crawlv1alpha1.Proxy
|
|
extraObjs []client.Object
|
|
stub *stubProvider
|
|
wantResult ctrl.Result
|
|
wantErr bool
|
|
verify func(t *testing.T, r *ProxyReconciler, stub *stubProvider)
|
|
}{
|
|
{
|
|
name: "managed without finalizer gets one and stops",
|
|
proxy: managedProxy(func(p *crawlv1alpha1.Proxy) {
|
|
p.Finalizers = nil
|
|
}),
|
|
stub: &stubProvider{},
|
|
wantResult: ctrl.Result{},
|
|
verify: func(t *testing.T, r *ProxyReconciler, stub *stubProvider) {
|
|
p := getProxy(t, r)
|
|
if len(p.Finalizers) != 1 || p.Finalizers[0] != crawlv1alpha1.FinalizerName {
|
|
t.Errorf("finalizers = %v, want [%s]", p.Finalizers, crawlv1alpha1.FinalizerName)
|
|
}
|
|
if stub.createCalls+stub.getCalls+stub.deleteCalls != 0 {
|
|
t.Errorf("provider was called before the finalizer was persisted")
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "empty providerID creates the instance",
|
|
proxy: managedProxy(),
|
|
stub: &stubProvider{createID: "stub-id-1"},
|
|
wantResult: ctrl.Result{RequeueAfter: tProvisioningPoll},
|
|
verify: func(t *testing.T, r *ProxyReconciler, stub *stubProvider) {
|
|
p := getProxy(t, r)
|
|
if p.Status.ProviderID != "stub-id-1" {
|
|
t.Errorf("providerID = %q, want stub-id-1", p.Status.ProviderID)
|
|
}
|
|
if got := p.Annotations[crawlv1alpha1.AnnotationSpecHash]; got != freshHash {
|
|
t.Errorf("spec-hash annotation = %q, want %q", got, freshHash)
|
|
}
|
|
assertCondition(t, p, crawlv1alpha1.ConditionProvisioned, metav1.ConditionFalse, ReasonProvisioning)
|
|
if p.Status.Phase != crawlv1alpha1.PhaseProvisioning {
|
|
t.Errorf("phase = %s, want Provisioning", p.Status.Phase)
|
|
}
|
|
want := provider.CreateRequest{
|
|
Name: provider.NameFromUID(testUID),
|
|
UID: string(testUID),
|
|
Namespace: testNamespace,
|
|
ProxyName: testProxyName,
|
|
Port: crawlv1alpha1.DefaultPort,
|
|
}
|
|
if stub.lastCreate != want {
|
|
t.Errorf("CreateRequest = %+v, want %+v", stub.lastCreate, want)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "provisioning instance polls again",
|
|
proxy: managedProxy(withProviderID("stub-id-1"), withSpecHashAnnotation(freshHash)),
|
|
stub: &stubProvider{getInst: &provider.Instance{ID: "stub-id-1", State: provider.StateProvisioning}},
|
|
wantResult: ctrl.Result{RequeueAfter: tProvisioningPoll},
|
|
verify: func(t *testing.T, r *ProxyReconciler, _ *stubProvider) {
|
|
p := getProxy(t, r)
|
|
if p.Status.IP != "" {
|
|
t.Errorf("ip = %q, want empty while provisioning", p.Status.IP)
|
|
}
|
|
assertCondition(t, p, crawlv1alpha1.ConditionProvisioned, metav1.ConditionFalse, ReasonProvisioning)
|
|
},
|
|
},
|
|
{
|
|
name: "running instance publishes IP",
|
|
proxy: managedProxy(withProviderID("stub-id-1"), withSpecHashAnnotation(freshHash)),
|
|
stub: &stubProvider{
|
|
getInst: &provider.Instance{ID: "stub-id-1", IP: "10.1.2.3", State: provider.StateRunning},
|
|
},
|
|
wantResult: ctrl.Result{RequeueAfter: tDriftPoll},
|
|
verify: func(t *testing.T, r *ProxyReconciler, _ *stubProvider) {
|
|
p := getProxy(t, r)
|
|
if p.Status.IP != "10.1.2.3" {
|
|
t.Errorf("ip = %q, want 10.1.2.3", p.Status.IP)
|
|
}
|
|
assertCondition(t, p, crawlv1alpha1.ConditionProvisioned, metav1.ConditionTrue, ReasonCreated)
|
|
if p.Status.Phase != crawlv1alpha1.PhaseProvisioning {
|
|
t.Errorf("phase = %s, want Provisioning until a health verdict exists", p.Status.Phase)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "stopped instance is deleted for recreation",
|
|
proxy: managedProxy(withProviderID("stub-id-1"), withSpecHashAnnotation(freshHash)),
|
|
stub: &stubProvider{getInst: &provider.Instance{ID: "stub-id-1", State: provider.StateStopped}},
|
|
wantResult: ctrl.Result{RequeueAfter: tDeletionPoll},
|
|
verify: func(t *testing.T, r *ProxyReconciler, stub *stubProvider) {
|
|
if stub.deleteCalls != 1 {
|
|
t.Errorf("deleteCalls = %d, want 1", stub.deleteCalls)
|
|
}
|
|
assertCondition(t, getProxy(t, r), crawlv1alpha1.ConditionProvisioned, metav1.ConditionFalse, ReasonRecreating)
|
|
},
|
|
},
|
|
{
|
|
name: "vanished instance clears ID for recreation",
|
|
proxy: managedProxy(withProviderID("stub-id-1"), withSpecHashAnnotation(freshHash),
|
|
func(p *crawlv1alpha1.Proxy) { p.Status.IP = "10.1.2.3" }),
|
|
stub: &stubProvider{getErr: notFoundErr()},
|
|
wantResult: ctrl.Result{RequeueAfter: tRequeueNow},
|
|
verify: func(t *testing.T, r *ProxyReconciler, _ *stubProvider) {
|
|
p := getProxy(t, r)
|
|
if p.Status.ProviderID != "" || p.Status.IP != "" {
|
|
t.Errorf("providerID/ip = %q/%q, want both cleared", p.Status.ProviderID, p.Status.IP)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "hash mismatch deletes the old instance",
|
|
proxy: managedProxy(withProviderID("stub-id-1"), withSpecHashAnnotation("stale-hash")),
|
|
stub: &stubProvider{
|
|
getInst: &provider.Instance{ID: "stub-id-1", IP: "10.1.2.3", State: provider.StateRunning},
|
|
},
|
|
wantResult: ctrl.Result{RequeueAfter: tDeletionPoll},
|
|
verify: func(t *testing.T, r *ProxyReconciler, stub *stubProvider) {
|
|
if stub.deleteCalls != 1 {
|
|
t.Errorf("deleteCalls = %d, want 1", stub.deleteCalls)
|
|
}
|
|
p := getProxy(t, r)
|
|
// The hash must not advance until the old instance is gone,
|
|
// or a crash would strand a half-replaced proxy.
|
|
if got := p.Annotations[crawlv1alpha1.AnnotationSpecHash]; got != "stale-hash" {
|
|
t.Errorf("spec-hash annotation = %q, want still stale-hash", got)
|
|
}
|
|
assertCondition(t, p, crawlv1alpha1.ConditionProvisioned, metav1.ConditionFalse, ReasonReplacing)
|
|
},
|
|
},
|
|
{
|
|
name: "empty annotation adopts instead of replacing",
|
|
proxy: managedProxy(withProviderID("stub-id-1")),
|
|
stub: &stubProvider{},
|
|
wantResult: ctrl.Result{RequeueAfter: tRequeueNow},
|
|
verify: func(t *testing.T, r *ProxyReconciler, stub *stubProvider) {
|
|
p := getProxy(t, r)
|
|
if got := p.Annotations[crawlv1alpha1.AnnotationSpecHash]; got != freshHash {
|
|
t.Errorf("spec-hash annotation = %q, want %q", got, freshHash)
|
|
}
|
|
if p.Status.ProviderID != "stub-id-1" {
|
|
t.Errorf("providerID = %q, want untouched stub-id-1", p.Status.ProviderID)
|
|
}
|
|
if stub.deleteCalls != 0 {
|
|
t.Errorf("deleteCalls = %d, want 0 — adoption must not replace", stub.deleteCalls)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "mismatch with instance gone advances the hash",
|
|
proxy: managedProxy(withProviderID("stub-id-1"), withSpecHashAnnotation("stale-hash")),
|
|
stub: &stubProvider{getErr: notFoundErr()},
|
|
wantResult: ctrl.Result{RequeueAfter: tRequeueNow},
|
|
verify: func(t *testing.T, r *ProxyReconciler, _ *stubProvider) {
|
|
p := getProxy(t, r)
|
|
if p.Status.ProviderID != "" {
|
|
t.Errorf("providerID = %q, want cleared", p.Status.ProviderID)
|
|
}
|
|
if got := p.Annotations[crawlv1alpha1.AnnotationSpecHash]; got != freshHash {
|
|
t.Errorf("spec-hash annotation = %q, want advanced to %q", got, freshHash)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "deletion with no providerID removes the finalizer",
|
|
proxy: managedProxy(deleting()),
|
|
stub: &stubProvider{},
|
|
wantResult: ctrl.Result{},
|
|
verify: func(t *testing.T, r *ProxyReconciler, stub *stubProvider) {
|
|
assertProxyGone(t, r)
|
|
if stub.deleteCalls != 0 {
|
|
t.Errorf("deleteCalls = %d, want 0", stub.deleteCalls)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "deletion with instance already gone removes the finalizer",
|
|
proxy: managedProxy(deleting(), withProviderID("stub-id-1")),
|
|
stub: &stubProvider{getErr: notFoundErr()},
|
|
wantResult: ctrl.Result{},
|
|
verify: func(t *testing.T, r *ProxyReconciler, _ *stubProvider) {
|
|
assertProxyGone(t, r)
|
|
},
|
|
},
|
|
{
|
|
name: "deletion deletes the instance and polls",
|
|
proxy: managedProxy(deleting(), withProviderID("stub-id-1")),
|
|
stub: &stubProvider{
|
|
getInst: &provider.Instance{ID: "stub-id-1", State: provider.StateRunning},
|
|
},
|
|
wantResult: ctrl.Result{RequeueAfter: tDeletionPoll},
|
|
verify: func(t *testing.T, r *ProxyReconciler, stub *stubProvider) {
|
|
if stub.deleteCalls != 1 {
|
|
t.Errorf("deleteCalls = %d, want 1", stub.deleteCalls)
|
|
}
|
|
p := getProxy(t, r)
|
|
if p.Status.Phase != crawlv1alpha1.PhaseDeleting {
|
|
t.Errorf("phase = %s, want Deleting", p.Status.Phase)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "external proxy tracks its endpoint without a finalizer",
|
|
proxy: managedProxy(func(p *crawlv1alpha1.Proxy) {
|
|
p.Finalizers = nil
|
|
p.Spec = crawlv1alpha1.ProxySpec{
|
|
Mode: crawlv1alpha1.ModeExternal,
|
|
Endpoint: &crawlv1alpha1.EndpointSpec{Host: "203.0.113.7", Port: 8080},
|
|
}
|
|
}),
|
|
stub: &stubProvider{},
|
|
wantResult: ctrl.Result{},
|
|
verify: func(t *testing.T, r *ProxyReconciler, stub *stubProvider) {
|
|
p := getProxy(t, r)
|
|
if p.Status.IP != "203.0.113.7" {
|
|
t.Errorf("ip = %q, want the endpoint host", p.Status.IP)
|
|
}
|
|
if len(p.Finalizers) != 0 {
|
|
t.Errorf("finalizers = %v, want none on External", p.Finalizers)
|
|
}
|
|
assertCondition(t, p, crawlv1alpha1.ConditionProvisioned, metav1.ConditionTrue, ReasonExternalEndpoint)
|
|
if stub.createCalls+stub.getCalls+stub.deleteCalls != 0 {
|
|
t.Errorf("provider was called for an External proxy")
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "quota error backs off slowly without failing",
|
|
proxy: managedProxy(),
|
|
stub: &stubProvider{
|
|
createErr: provider.Wrap(provider.ErrQuotaExceeded, "create", "stub", "", nil),
|
|
},
|
|
wantResult: ctrl.Result{RequeueAfter: tQuotaRetry},
|
|
verify: func(t *testing.T, r *ProxyReconciler, _ *stubProvider) {
|
|
p := getProxy(t, r)
|
|
assertCondition(t, p, crawlv1alpha1.ConditionProvisioned, metav1.ConditionFalse, ReasonQuotaExceeded)
|
|
if p.Status.Phase == crawlv1alpha1.PhaseFailed {
|
|
t.Errorf("phase = Failed, want anything but — quota is a wait, not a failure")
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "permanent error latches Failed and stops calling the provider",
|
|
proxy: managedProxy(),
|
|
stub: &stubProvider{
|
|
createErr: provider.Wrap(provider.ErrPermanent, "create", "stub", "", nil),
|
|
},
|
|
wantResult: ctrl.Result{},
|
|
verify: func(t *testing.T, r *ProxyReconciler, stub *stubProvider) {
|
|
p := getProxy(t, r)
|
|
assertCondition(t, p, crawlv1alpha1.ConditionProvisioned, metav1.ConditionFalse, ReasonPermanentError)
|
|
if p.Status.Phase != crawlv1alpha1.PhaseFailed {
|
|
t.Errorf("phase = %s, want Failed", p.Status.Phase)
|
|
}
|
|
if res, err := doReconcile(t, r); err != nil || res != (ctrl.Result{}) {
|
|
t.Errorf("second reconcile = %+v, %v; want empty result, nil", res, err)
|
|
}
|
|
if stub.createCalls != 1 {
|
|
t.Errorf("createCalls = %d after latch, want 1", stub.createCalls)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "transient error is returned for workqueue backoff",
|
|
proxy: managedProxy(),
|
|
stub: &stubProvider{
|
|
createErr: provider.Wrap(provider.ErrTransient, "create", "stub", "", nil),
|
|
},
|
|
wantResult: ctrl.Result{},
|
|
wantErr: true,
|
|
verify: func(t *testing.T, r *ProxyReconciler, _ *stubProvider) {
|
|
p := getProxy(t, r)
|
|
if p.Status.Phase != crawlv1alpha1.PhasePending {
|
|
t.Errorf("phase = %s, want still Pending", p.Status.Phase)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "unconfigured provider is a permanent failure",
|
|
proxy: managedProxy(func(p *crawlv1alpha1.Proxy) {
|
|
p.Spec.Provider = "no-such-provider"
|
|
}),
|
|
stub: &stubProvider{},
|
|
wantResult: ctrl.Result{},
|
|
verify: func(t *testing.T, r *ProxyReconciler, _ *stubProvider) {
|
|
p := getProxy(t, r)
|
|
assertCondition(t, p, crawlv1alpha1.ConditionProvisioned, metav1.ConditionFalse, ReasonPermanentError)
|
|
if p.Status.Phase != crawlv1alpha1.PhaseFailed {
|
|
t.Errorf("phase = %s, want Failed", p.Status.Phase)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "cloud-init secret is resolved into the create request",
|
|
proxy: managedProxy(func(p *crawlv1alpha1.Proxy) {
|
|
p.Spec.CloudInit = &crawlv1alpha1.CloudInitSpec{
|
|
SecretRef: &crawlv1alpha1.SecretKeySelector{Name: "ci-secret"},
|
|
}
|
|
}),
|
|
extraObjs: []client.Object{
|
|
&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "ci-secret", Namespace: testNamespace},
|
|
Data: map[string][]byte{"user-data": []byte("#cloud-config\npackages: [squid]")},
|
|
},
|
|
},
|
|
stub: &stubProvider{createID: "stub-id-1"},
|
|
wantResult: ctrl.Result{RequeueAfter: tProvisioningPoll},
|
|
verify: func(t *testing.T, r *ProxyReconciler, stub *stubProvider) {
|
|
if want := "#cloud-config\npackages: [squid]"; stub.lastCreate.CloudInit != want {
|
|
t.Errorf("CreateRequest.CloudInit = %q, want the resolved secret content", stub.lastCreate.CloudInit)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "missing cloud-init secret errors and marks the condition",
|
|
proxy: managedProxy(func(p *crawlv1alpha1.Proxy) {
|
|
p.Spec.CloudInit = &crawlv1alpha1.CloudInitSpec{
|
|
SecretRef: &crawlv1alpha1.SecretKeySelector{Name: "absent"},
|
|
}
|
|
}),
|
|
stub: &stubProvider{},
|
|
wantResult: ctrl.Result{},
|
|
wantErr: true,
|
|
verify: func(t *testing.T, r *ProxyReconciler, stub *stubProvider) {
|
|
assertCondition(t, getProxy(t, r), crawlv1alpha1.ConditionProvisioned, metav1.ConditionFalse, ReasonCloudInitError)
|
|
if stub.createCalls != 0 {
|
|
t.Errorf("createCalls = %d, want 0 with unresolved cloud-init", stub.createCalls)
|
|
}
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
objs := append([]client.Object{tc.proxy}, tc.extraObjs...)
|
|
r := newTestReconciler(t, tc.stub, objs...)
|
|
res, err := doReconcile(t, r)
|
|
if (err != nil) != tc.wantErr {
|
|
t.Fatalf("Reconcile error = %v, wantErr %v", err, tc.wantErr)
|
|
}
|
|
if res != tc.wantResult {
|
|
t.Errorf("Result = %+v, want %+v", res, tc.wantResult)
|
|
}
|
|
tc.verify(t, r, tc.stub)
|
|
})
|
|
}
|
|
}
|
|
|
|
func assertProxyGone(t *testing.T, r *ProxyReconciler) {
|
|
t.Helper()
|
|
var p crawlv1alpha1.Proxy
|
|
err := r.Get(context.Background(), types.NamespacedName{Namespace: testNamespace, Name: testProxyName}, &p)
|
|
if !apierrors.IsNotFound(err) {
|
|
t.Errorf("proxy still exists (err=%v), want NotFound after finalizer removal", err)
|
|
}
|
|
}
|