Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Don't add clientca-configmap finalizer if deleting
Don't add the clientca-configmap finalizer to an IngressController if it is marked for deletion. Before this commit, the clientca-configmap controller would attempt to add (or re-add) its finalizer to an IngressController that had been marked for deletion. This attempt resulted in the following error from the API: IngressController.operator.openshift.io "test-client-ca-configmap" is invalid: metadata.finalizers: Forbidden: no new finalizers can be added if the object is being deleted, found new finalizers []string{"ingresscontroller.operator.openshift.io/finalizer-clientca-configmap"} The controller would retry adding the finalizer repeatedly, causing the same error to be repeatedly logged until the deletion completed. This commit fixes OCPBUGS-14994. https://issues.redhat.com/browse/OCPBUGS-14994 * pkg/operator/controller/clientca-configmap/controller.go (Reconcile): Only attempt to add the finalizer if the IngressController is not marked for deletion. * pkg/operator/controller/clientca-configmap/controller_test.go: New file. (Test_Reconcile): New test. Verify that Reconcile adds and removes the finalizer and creates, updates, or deletes the target configmap as appropriate. (fakeClientRecorder): New type, used in Test_Reconcile. (Create, Delete, Update): New methods for fakeClientRecorder to implement the controller-runtime client.Client interface.
- Loading branch information
Showing
2 changed files
with
305 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
304 changes: 304 additions & 0 deletions
304
pkg/operator/controller/clientca-configmap/controller_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,304 @@ | ||
package clientcaconfigmap | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
"time" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"github.com/google/go-cmp/cmp/cmpopts" | ||
"github.com/stretchr/testify/assert" | ||
|
||
"github.com/openshift/cluster-ingress-operator/pkg/manifests" | ||
|
||
configv1 "github.com/openshift/api/config/v1" | ||
operatorv1 "github.com/openshift/api/operator/v1" | ||
|
||
corev1 "k8s.io/api/core/v1" | ||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
"k8s.io/apimachinery/pkg/types" | ||
|
||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
"sigs.k8s.io/controller-runtime/pkg/client/fake" | ||
"sigs.k8s.io/controller-runtime/pkg/reconcile" | ||
) | ||
|
||
// Test_Reconcile verifies that the controller's Reconcile method behaves | ||
// correctly. | ||
func Test_Reconcile(t *testing.T) { | ||
exampleTime, err := time.Parse(time.DateTime, "2006-01-02 15:04:05") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
ic := func(deleted bool, configmapName string, additionalFinalizers ...string) *operatorv1.IngressController { | ||
var ts *metav1.Time | ||
if deleted { | ||
ts = &metav1.Time{Time: exampleTime} | ||
} | ||
return &operatorv1.IngressController{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
DeletionTimestamp: ts, | ||
Finalizers: append([]string{manifests.IngressControllerFinalizer}, additionalFinalizers...), | ||
Namespace: "openshift-ingress-operator", | ||
Name: "test", | ||
}, | ||
Spec: operatorv1.IngressControllerSpec{ | ||
ClientTLS: operatorv1.ClientTLS{ | ||
ClientCA: configv1.ConfigMapNameReference{ | ||
Name: configmapName, | ||
}, | ||
}, | ||
}, | ||
} | ||
} | ||
cm := func(namespace, name string, data map[string]string) *corev1.ConfigMap { | ||
return &corev1.ConfigMap{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Namespace: namespace, | ||
Name: name, | ||
}, | ||
Data: data, | ||
} | ||
} | ||
var ( | ||
expectedData = map[string]string{ | ||
"ca-bundle.pem": "certificate", | ||
} | ||
unexpectedData = map[string]string{ | ||
"garbage": "garbage", | ||
} | ||
) | ||
tests := []struct { | ||
name string | ||
existingObjects []runtime.Object | ||
expectCreate []client.Object | ||
expectUpdate []client.Object | ||
expectDelete []client.Object | ||
}{ | ||
{ | ||
name: "ingresscontroller absent", | ||
existingObjects: []runtime.Object{}, | ||
expectCreate: []client.Object{}, | ||
expectUpdate: []client.Object{}, | ||
expectDelete: []client.Object{}, | ||
}, | ||
{ | ||
name: "no configmap specified", | ||
existingObjects: []runtime.Object{ | ||
ic(false, ""), | ||
}, | ||
expectCreate: []client.Object{}, | ||
expectUpdate: []client.Object{}, | ||
expectDelete: []client.Object{}, | ||
}, | ||
{ | ||
name: "source configmap absent, target configmap absent, finalizer absent", | ||
existingObjects: []runtime.Object{ | ||
ic(false, "ca-bundle"), | ||
}, | ||
expectCreate: []client.Object{}, | ||
expectUpdate: []client.Object{ | ||
ic(false, "ca-bundle", "ingresscontroller.operator.openshift.io/finalizer-clientca-configmap"), | ||
}, | ||
expectDelete: []client.Object{}, | ||
}, | ||
{ | ||
name: "source configmap absent, target configmap absent, finalizer present", | ||
existingObjects: []runtime.Object{ | ||
ic(false, "ca-bundle", "ingresscontroller.operator.openshift.io/finalizer-clientca-configmap"), | ||
}, | ||
expectCreate: []client.Object{}, | ||
expectUpdate: []client.Object{}, | ||
expectDelete: []client.Object{}, | ||
}, | ||
{ | ||
name: "source configmap absent, target configmap present, finalizer absent", | ||
existingObjects: []runtime.Object{ | ||
ic(false, "ca-bundle"), | ||
cm("openshift-ingress", "router-client-ca-test", expectedData), | ||
}, | ||
expectCreate: []client.Object{}, | ||
expectUpdate: []client.Object{ | ||
ic(false, "ca-bundle", "ingresscontroller.operator.openshift.io/finalizer-clientca-configmap"), | ||
}, | ||
expectDelete: []client.Object{ | ||
cm("openshift-ingress", "router-client-ca-test", expectedData), | ||
}, | ||
}, | ||
{ | ||
name: "source configmap absent, target configmap present, finalizer present", | ||
existingObjects: []runtime.Object{ | ||
ic(false, "ca-bundle", "ingresscontroller.operator.openshift.io/finalizer-clientca-configmap"), | ||
cm("openshift-ingress", "router-client-ca-test", expectedData), | ||
}, | ||
expectCreate: []client.Object{}, | ||
expectUpdate: []client.Object{}, | ||
expectDelete: []client.Object{ | ||
cm("openshift-ingress", "router-client-ca-test", expectedData), | ||
}, | ||
}, | ||
{ | ||
name: "source configmap present, target configmap absent, finalizer absent", | ||
existingObjects: []runtime.Object{ | ||
ic(false, "ca-bundle"), | ||
cm("openshift-config", "ca-bundle", expectedData), | ||
}, | ||
expectCreate: []client.Object{ | ||
cm("openshift-ingress", "router-client-ca-test", expectedData), | ||
}, | ||
expectUpdate: []client.Object{ | ||
ic(false, "ca-bundle", "ingresscontroller.operator.openshift.io/finalizer-clientca-configmap"), | ||
}, | ||
expectDelete: []client.Object{}, | ||
}, | ||
{ | ||
name: "source configmap present, target configmap absent, finalizer present", | ||
existingObjects: []runtime.Object{ | ||
ic(false, "ca-bundle", "ingresscontroller.operator.openshift.io/finalizer-clientca-configmap"), | ||
cm("openshift-config", "ca-bundle", expectedData), | ||
}, | ||
expectCreate: []client.Object{ | ||
cm("openshift-ingress", "router-client-ca-test", expectedData), | ||
}, | ||
expectUpdate: []client.Object{}, | ||
expectDelete: []client.Object{}, | ||
}, | ||
{ | ||
name: "source configmap present, target configmap present, finalizer absent", | ||
existingObjects: []runtime.Object{ | ||
ic(false, "ca-bundle"), | ||
cm("openshift-config", "ca-bundle", expectedData), | ||
cm("openshift-ingress", "router-client-ca-test", expectedData), | ||
}, | ||
expectCreate: []client.Object{}, | ||
expectUpdate: []client.Object{ | ||
ic(false, "ca-bundle", "ingresscontroller.operator.openshift.io/finalizer-clientca-configmap"), | ||
}, | ||
expectDelete: []client.Object{}, | ||
}, | ||
{ | ||
name: "source configmap present, target configmap present but inconsistent, finalizer present", | ||
existingObjects: []runtime.Object{ | ||
ic(false, "ca-bundle", "ingresscontroller.operator.openshift.io/finalizer-clientca-configmap"), | ||
cm("openshift-config", "ca-bundle", expectedData), | ||
cm("openshift-ingress", "router-client-ca-test", unexpectedData), | ||
}, | ||
expectCreate: []client.Object{}, | ||
expectUpdate: []client.Object{ | ||
cm("openshift-ingress", "router-client-ca-test", expectedData), | ||
}, | ||
expectDelete: []client.Object{}, | ||
}, | ||
{ | ||
name: "source configmap present, target configmap absent, finalizer absent, ingresscontroller deleted", | ||
existingObjects: []runtime.Object{ | ||
ic(true, "ca-bundle"), | ||
cm("openshift-config", "ca-bundle", expectedData), | ||
}, | ||
expectCreate: []client.Object{}, | ||
expectUpdate: []client.Object{}, | ||
expectDelete: []client.Object{}, | ||
}, | ||
{ | ||
name: "source configmap present, target configmap present, finalizer absent, ingresscontroller deleted", | ||
existingObjects: []runtime.Object{ | ||
ic(true, "ca-bundle"), | ||
cm("openshift-config", "ca-bundle", expectedData), | ||
cm("openshift-ingress", "router-client-ca-test", expectedData), | ||
}, | ||
expectCreate: []client.Object{}, | ||
expectUpdate: []client.Object{}, | ||
expectDelete: []client.Object{ | ||
cm("openshift-ingress", "router-client-ca-test", expectedData), | ||
}, | ||
}, | ||
{ | ||
name: "source configmap present, target configmap present but inconsistent, finalizer present, ingresscontroller deleted", | ||
existingObjects: []runtime.Object{ | ||
ic(true, "ca-bundle", "ingresscontroller.operator.openshift.io/finalizer-clientca-configmap"), | ||
cm("openshift-config", "ca-bundle", expectedData), | ||
cm("openshift-ingress", "router-client-ca-test", unexpectedData), | ||
}, | ||
expectCreate: []client.Object{}, | ||
expectUpdate: []client.Object{ | ||
ic(true, "ca-bundle"), | ||
}, | ||
expectDelete: []client.Object{ | ||
cm("openshift-ingress", "router-client-ca-test", unexpectedData), | ||
}, | ||
}, | ||
} | ||
|
||
scheme := runtime.NewScheme() | ||
operatorv1.Install(scheme) | ||
corev1.AddToScheme(scheme) | ||
|
||
for _, tc := range tests { | ||
t.Run(tc.name, func(t *testing.T) { | ||
fakeClient := fake.NewClientBuilder(). | ||
WithScheme(scheme). | ||
WithRuntimeObjects(tc.existingObjects...). | ||
Build() | ||
cl := &fakeClientRecorder{fakeClient, t, []client.Object{}, []client.Object{}, []client.Object{}} | ||
reconciler := &reconciler{ | ||
client: cl, | ||
config: Config{ | ||
OperatorNamespace: "openshift-ingress-operator", | ||
SourceNamespace: "openshift-config", | ||
TargetNamespace: "openshift-ingress", | ||
}, | ||
} | ||
req := reconcile.Request{ | ||
NamespacedName: types.NamespacedName{ | ||
Namespace: "openshift-ingress-operator", | ||
Name: "test", | ||
}, | ||
} | ||
res, err := reconciler.Reconcile(context.Background(), req) | ||
assert.NoError(t, err) | ||
assert.Equal(t, reconcile.Result{}, res) | ||
cmpOpts := []cmp.Option{ | ||
cmpopts.EquateEmpty(), | ||
cmpopts.IgnoreFields(metav1.ObjectMeta{}, "Annotations", "ResourceVersion"), | ||
cmpopts.IgnoreFields(metav1.TypeMeta{}, "Kind", "APIVersion"), | ||
cmpopts.IgnoreFields(apiextensionsv1.CustomResourceDefinition{}, "Spec"), | ||
} | ||
if diff := cmp.Diff(tc.expectCreate, cl.added, cmpOpts...); diff != "" { | ||
t.Fatalf("found diff between expected and actual creates: %s", diff) | ||
} | ||
if diff := cmp.Diff(tc.expectUpdate, cl.updated, cmpOpts...); diff != "" { | ||
t.Fatalf("found diff between expected and actual updates: %s", diff) | ||
} | ||
if diff := cmp.Diff(tc.expectDelete, cl.deleted, cmpOpts...); diff != "" { | ||
t.Fatalf("found diff between expected and actual deletes: %s", diff) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
// fakeClientRecorder is a fake client that records adds, updates, and deletes. | ||
type fakeClientRecorder struct { | ||
client.Client | ||
*testing.T | ||
|
||
added []client.Object | ||
updated []client.Object | ||
deleted []client.Object | ||
} | ||
|
||
func (c *fakeClientRecorder) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { | ||
c.added = append(c.added, obj) | ||
return c.Client.Create(ctx, obj, opts...) | ||
} | ||
|
||
func (c *fakeClientRecorder) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { | ||
c.deleted = append(c.deleted, obj) | ||
return c.Client.Delete(ctx, obj, opts...) | ||
} | ||
|
||
func (c *fakeClientRecorder) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { | ||
c.updated = append(c.updated, obj) | ||
return c.Client.Update(ctx, obj, opts...) | ||
} |