Skip to content

Commit 270ea6f

Browse files
author
Moritz Clasmeier
committed
Updater tests
1 parent 8562794 commit 270ea6f

1 file changed

Lines changed: 100 additions & 2 deletions

File tree

pkg/reconciler/internal/updater/updater_test.go

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"errors"
2222

23+
"github.com/go-logr/logr"
2324
. "github.com/onsi/ginkgo/v2"
2425
. "github.com/onsi/gomega"
2526

@@ -31,7 +32,9 @@ import (
3132
"sigs.k8s.io/controller-runtime/pkg/client"
3233
"sigs.k8s.io/controller-runtime/pkg/client/fake"
3334
"sigs.k8s.io/controller-runtime/pkg/client/interceptor"
35+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3436

37+
pkgStatus "github.com/operator-framework/helm-operator-plugins/pkg/internal/status"
3538
"github.com/operator-framework/helm-operator-plugins/pkg/reconciler/internal/conditions"
3639
)
3740

@@ -41,6 +44,10 @@ const (
4144
replicasStatus = int64(5)
4245
)
4346

47+
var (
48+
transientError = errors.New("transient error")
49+
)
50+
4451
var _ = Describe("Updater", func() {
4552
var (
4653
cl client.Client
@@ -51,7 +58,7 @@ var _ = Describe("Updater", func() {
5158

5259
JustBeforeEach(func() {
5360
cl = fake.NewClientBuilder().WithInterceptorFuncs(interceptorFuncs).Build()
54-
u = New(cl)
61+
u = New(cl, logr.Discard())
5562
obj = &unstructured.Unstructured{Object: map[string]interface{}{
5663
"apiVersion": "apps/v1",
5764
"kind": "Deployment",
@@ -85,7 +92,7 @@ var _ = Describe("Updater", func() {
8592
interceptorFuncs.SubResourceUpdate = func(ctx context.Context, interceptorClient client.Client, subResourceName string, obj client.Object, opts ...client.SubResourceUpdateOption) error {
8693
updateCallCount++
8794
if updateCallCount == 1 {
88-
return errors.New("transient error")
95+
return transientError
8996
}
9097
return interceptorClient.SubResource(subResourceName).Update(ctx, obj, opts...)
9198
}
@@ -177,9 +184,85 @@ var _ = Describe("Updater", func() {
177184
Expect(found).To(BeTrue())
178185
Expect(err).To(Succeed())
179186
})
187+
188+
It("should add a finalizer", func() {
189+
u.Update(func(u *unstructured.Unstructured) bool {
190+
return controllerutil.AddFinalizer(u, testFinalizer)
191+
})
192+
Expect(u.Apply(context.TODO(), obj)).To(Succeed())
193+
Expect(cl.Get(context.TODO(), types.NamespacedName{Namespace: "testNamespace", Name: "testDeployment"}, obj)).To(Succeed())
194+
Expect(obj.GetFinalizers()).To(ContainElement(testFinalizer))
195+
})
196+
197+
It("should remove a finalizer", func() {
198+
obj.SetFinalizers([]string{testFinalizer})
199+
Expect(cl.Update(context.TODO(), obj)).To(Succeed())
200+
201+
u.Update(func(u *unstructured.Unstructured) bool {
202+
return controllerutil.RemoveFinalizer(u, testFinalizer)
203+
})
204+
Expect(u.Apply(context.TODO(), obj)).To(Succeed())
205+
Expect(cl.Get(context.TODO(), types.NamespacedName{Namespace: "testNamespace", Name: "testDeployment"}, obj)).To(Succeed())
206+
Expect(obj.GetFinalizers()).ToNot(ContainElement(testFinalizer))
207+
})
208+
209+
It("should preserve unknown status conditions", func() {
210+
// Add external status condition on cluster.
211+
clusterObj := obj.DeepCopy()
212+
unknownCondition := map[string]interface{}{
213+
"type": "UnknownCondition",
214+
"status": string(corev1.ConditionTrue),
215+
"reason": "ExternallyManaged",
216+
}
217+
Expect(unstructured.SetNestedSlice(clusterObj.Object, []interface{}{unknownCondition}, "status", "conditions")).To(Succeed())
218+
Expect(retryOnTransientError(func() error {
219+
return cl.Status().Update(context.TODO(), clusterObj)
220+
})).ToNot(HaveOccurred())
221+
// Add status condition using updater.
222+
u.UpdateStatus(EnsureCondition(conditions.Deployed(corev1.ConditionTrue, "", "")))
223+
u.EnableAggressiveConflictResolution()
224+
Expect(u.Apply(context.TODO(), obj)).To(Succeed())
225+
// Retrieve object from cluster and extract status conditions.
226+
Expect(cl.Get(context.TODO(), types.NamespacedName{Namespace: "testNamespace", Name: "testDeployment"}, obj)).To(Succeed())
227+
objConditionsSlice, _, err := unstructured.NestedSlice(obj.Object, "status", "conditions")
228+
Expect(err).ToNot(HaveOccurred())
229+
objConditions, err := pkgStatus.FromUnstructured(objConditionsSlice)
230+
Expect(err).ToNot(HaveOccurred())
231+
// Verify both status conditions are present.
232+
Expect(objConditions.IsTrueFor(pkgStatus.ConditionType("UnknownCondition"))).To(BeTrue())
233+
Expect(objConditions.IsTrueFor(pkgStatus.ConditionType("Deployed"))).To(BeTrue())
234+
})
235+
236+
It("should fail on conflict without aggressive resolution", func() {
237+
// Add external status condition on cluster.
238+
clusterObj := obj.DeepCopy()
239+
unknownCondition := map[string]interface{}{
240+
"type": "UnknownCondition",
241+
"status": string(corev1.ConditionTrue),
242+
"reason": "ExternallyManaged",
243+
}
244+
Expect(unstructured.SetNestedSlice(clusterObj.Object, []interface{}{unknownCondition}, "status", "conditions")).To(Succeed())
245+
Expect(retryOnTransientError(func() error {
246+
return cl.Status().Update(context.TODO(), clusterObj)
247+
})).ToNot(HaveOccurred())
248+
Expect(cl.Status().Update(context.TODO(), clusterObj)).To(Succeed())
249+
// Add status condition using updater.
250+
u.UpdateStatus(EnsureCondition(conditions.Deployed(corev1.ConditionTrue, "", "")))
251+
err := u.Apply(context.TODO(), obj)
252+
// Verify conflict error is returned.
253+
Expect(apierrors.IsConflict(err)).To(BeTrue())
254+
})
180255
})
181256
})
182257

258+
func retryOnTransientError(f func() error) error {
259+
err := f()
260+
if errors.Is(err, transientError) {
261+
err = f()
262+
}
263+
return err
264+
}
265+
183266
var _ = Describe("RemoveFinalizer", func() {
184267
var obj *unstructured.Unstructured
185268

@@ -325,4 +408,19 @@ var _ = Describe("statusFor", func() {
325408
obj.Object["status"] = "hello"
326409
Expect(statusFor(obj)).To(Equal(&helmAppStatus{}))
327410
})
411+
412+
It("should handle unknown status conditions", func() {
413+
uSt := map[string]interface{}{
414+
"conditions": []interface{}{
415+
map[string]interface{}{
416+
"type": "UnknownCondition",
417+
"status": string(corev1.ConditionTrue),
418+
},
419+
},
420+
}
421+
obj.Object["status"] = uSt
422+
status := statusFor(obj)
423+
Expect(status).ToNot(BeNil())
424+
Expect(status.Conditions.IsTrueFor(pkgStatus.ConditionType("UnknownCondition"))).To(BeTrue())
425+
})
328426
})

0 commit comments

Comments
 (0)