Skip to content

Commit 06c8bfd

Browse files
committed
Add annotation to override default timeout
In the e2e test we have to wait for the default bundle unpack timeout of 10m to expire. Adding an annotation to set a timeout per InstallPlan lets us override the default unpack timeout for a faster e2e test.
1 parent 260bc91 commit 06c8bfd

File tree

5 files changed

+95
-55
lines changed

5 files changed

+95
-55
lines changed

pkg/controller/bundle/bundle_unpacker.go

Lines changed: 61 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ const (
3131
// TODO: Move to operator-framework/api/pkg/operators/v1alpha1
3232
// BundleLookupFailed describes conditions types for when BundleLookups fail
3333
BundleLookupFailed operatorsv1alpha1.BundleLookupConditionType = "BundleLookupFailed"
34+
35+
// TODO: This can be a spec field
36+
// BundleUnpackTimeoutAnnotationKey allows setting a bundle unpack timeout per InstallPlan
37+
// and overrides the default specified by the --bundle-unpack-timeout flag
38+
// The time duration should be in the same format as accepted by time.ParseDuration()
39+
// e.g 1m30s
40+
BundleUnpackTimeoutAnnotationKey = "bundle-unpack-timeout"
3441
)
3542

3643
type BundleUnpackResult struct {
@@ -74,7 +81,7 @@ func newBundleUnpackResult(lookup *operatorsv1alpha1.BundleLookup) *BundleUnpack
7481
}
7582
}
7683

77-
func (c *ConfigMapUnpacker) job(cmRef *corev1.ObjectReference, bundlePath string, secrets []corev1.LocalObjectReference) *batchv1.Job {
84+
func (c *ConfigMapUnpacker) job(cmRef *corev1.ObjectReference, bundlePath string, secrets []corev1.LocalObjectReference, ipAnnotations map[string]string) *batchv1.Job {
7885
job := &batchv1.Job{
7986
Spec: batchv1.JobSpec{
8087
//ttlSecondsAfterFinished: 0 // can use in the future to not have to clean up job
@@ -178,12 +185,6 @@ func (c *ConfigMapUnpacker) job(cmRef *corev1.ObjectReference, bundlePath string
178185
job.SetName(cmRef.Name)
179186
job.SetOwnerReferences([]metav1.OwnerReference{ownerRef(cmRef)})
180187

181-
// Don't set a timeout if it is 0
182-
if c.unpackTimeout != time.Duration(0) {
183-
t := int64(c.unpackTimeout.Seconds())
184-
job.Spec.ActiveDeadlineSeconds = &t
185-
}
186-
187188
// By default the BackoffLimit is set to 6 which with exponential backoff 10s + 20s + 40s ...
188189
// translates to ~10m of waiting time.
189190
// We want to fail faster than that when we have repeated failures from the bundle unpack pod
@@ -193,13 +194,38 @@ func (c *ConfigMapUnpacker) job(cmRef *corev1.ObjectReference, bundlePath string
193194
backOffLimit := int32(3)
194195
job.Spec.BackoffLimit = &backOffLimit
195196

197+
// Set ActiveDeadlineSeconds as the unpack timeout
198+
// Don't set a timeout if it is 0
199+
if c.unpackTimeout != time.Duration(0) {
200+
t := int64(c.unpackTimeout.Seconds())
201+
job.Spec.ActiveDeadlineSeconds = &t
202+
}
203+
204+
// The bundle timeout annotation if specified overrides the --bundle-unpack-timeout flag value
205+
timeoutStr, ok := ipAnnotations[BundleUnpackTimeoutAnnotationKey]
206+
if !ok {
207+
return job
208+
}
209+
d, err := time.ParseDuration(timeoutStr)
210+
if err != nil {
211+
// TODO(haseeb): log this error
212+
return job
213+
}
214+
if d == time.Duration(0) {
215+
// 0 means no timeout
216+
return job
217+
}
218+
219+
timeoutSeconds := int64(d.Seconds())
220+
job.Spec.ActiveDeadlineSeconds = &timeoutSeconds
221+
196222
return job
197223
}
198224

199225
//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . Unpacker
200226

201227
type Unpacker interface {
202-
UnpackBundle(lookup *operatorsv1alpha1.BundleLookup) (result *BundleUnpackResult, err error)
228+
UnpackBundle(lookup *operatorsv1alpha1.BundleLookup, ipAnnotations map[string]string) (result *BundleUnpackResult, err error)
203229
}
204230

205231
type ConfigMapUnpacker struct {
@@ -346,7 +372,8 @@ const (
346372
NotUnpackedMessage = "bundle contents have not yet been persisted to installplan status"
347373
)
348374

349-
func (c *ConfigMapUnpacker) UnpackBundle(lookup *operatorsv1alpha1.BundleLookup) (result *BundleUnpackResult, err error) {
375+
func (c *ConfigMapUnpacker) UnpackBundle(lookup *operatorsv1alpha1.BundleLookup, ipAnnotations map[string]string) (result *BundleUnpackResult, err error) {
376+
350377
result = newBundleUnpackResult(lookup)
351378

352379
// if bundle lookup failed condition already present, then there is nothing more to do
@@ -355,26 +382,22 @@ func (c *ConfigMapUnpacker) UnpackBundle(lookup *operatorsv1alpha1.BundleLookup)
355382
return result, nil
356383
}
357384

358-
// if pending condition is missing, bundle has already been unpacked
359-
cond := result.GetCondition(operatorsv1alpha1.BundleLookupPending)
360-
if cond.Status == corev1.ConditionUnknown {
361-
return result, nil
362-
}
363-
// if pending condition is false, bundle unpack has already failed so nothing more to do
364-
if cond.Status == corev1.ConditionFalse {
385+
// if pending condition is not true then bundle has already been unpacked(unknown) or failed(false)
386+
pendingCond := result.GetCondition(operatorsv1alpha1.BundleLookupPending)
387+
if pendingCond.Status != corev1.ConditionTrue {
365388
return result, nil
366389
}
367390

368391
now := c.now()
369392

370393
var cs *operatorsv1alpha1.CatalogSource
371394
if cs, err = c.csLister.CatalogSources(result.CatalogSourceRef.Namespace).Get(result.CatalogSourceRef.Name); err != nil {
372-
if apierrors.IsNotFound(err) && cond.Reason != CatalogSourceMissingReason {
373-
cond.Status = corev1.ConditionTrue
374-
cond.Reason = CatalogSourceMissingReason
375-
cond.Message = CatalogSourceMissingMessage
376-
cond.LastTransitionTime = &now
377-
result.SetCondition(cond)
395+
if apierrors.IsNotFound(err) && pendingCond.Reason != CatalogSourceMissingReason {
396+
pendingCond.Status = corev1.ConditionTrue
397+
pendingCond.Reason = CatalogSourceMissingReason
398+
pendingCond.Message = CatalogSourceMissingMessage
399+
pendingCond.LastTransitionTime = &now
400+
result.SetCondition(pendingCond)
378401
err = nil
379402
}
380403

@@ -412,7 +435,7 @@ func (c *ConfigMapUnpacker) UnpackBundle(lookup *operatorsv1alpha1.BundleLookup)
412435
secrets = append(secrets, corev1.LocalObjectReference{Name: secretName})
413436
}
414437
var job *batchv1.Job
415-
job, err = c.ensureJob(cmRef, result.Path, secrets)
438+
job, err = c.ensureJob(cmRef, result.Path, secrets, ipAnnotations)
416439
if err != nil {
417440
return
418441
}
@@ -429,18 +452,16 @@ func (c *ConfigMapUnpacker) UnpackBundle(lookup *operatorsv1alpha1.BundleLookup)
429452
result.SetCondition(failedCond)
430453

431454
// BundleLookupPending is false with reason being job failed
432-
cond.Status = corev1.ConditionFalse
433-
cond.Reason = JobFailedReason
434-
cond.Message = JobFailedMessage
435-
cond.LastTransitionTime = &now
436-
result.SetCondition(cond)
455+
pendingCond.Status = corev1.ConditionFalse
456+
pendingCond.Reason = JobFailedReason
457+
pendingCond.Message = JobFailedMessage
458+
pendingCond.LastTransitionTime = &now
459+
result.SetCondition(pendingCond)
437460

438461
return
439462
}
440463

441-
isComplete, _ := jobConditionTrue(job, batchv1.JobComplete)
442-
if !isComplete {
443-
464+
if isComplete, _ := jobConditionTrue(job, batchv1.JobComplete); !isComplete {
444465
// In the case of an image pull failure for a non-existent image the bundle unpack job
445466
// can stay pending until the ActiveDeadlineSeconds timeout ~10m
446467
// To indicate why it's pending we inspect the container statuses of the
@@ -457,12 +478,12 @@ func (c *ConfigMapUnpacker) UnpackBundle(lookup *operatorsv1alpha1.BundleLookup)
457478
}
458479

459480
// Update BundleLookupPending condition if there are any changes
460-
if cond.Status != corev1.ConditionTrue || cond.Reason != JobIncompleteReason || cond.Message != pendingMessage {
461-
cond.Status = corev1.ConditionTrue
462-
cond.Reason = JobIncompleteReason
463-
cond.Message = pendingMessage
464-
cond.LastTransitionTime = &now
465-
result.SetCondition(cond)
481+
if pendingCond.Status != corev1.ConditionTrue || pendingCond.Reason != JobIncompleteReason || pendingCond.Message != pendingMessage {
482+
pendingCond.Status = corev1.ConditionTrue
483+
pendingCond.Reason = JobIncompleteReason
484+
pendingCond.Message = pendingMessage
485+
pendingCond.LastTransitionTime = &now
486+
result.SetCondition(pendingCond)
466487
}
467488

468489
return
@@ -516,7 +537,7 @@ func (c *ConfigMapUnpacker) pendingContainerStatusMessages(job *batchv1.Job) (st
516537

517538
// Aggregate the wait reasons for all pending containers
518539
containerStatusMessages = containerStatusMessages +
519-
fmt.Sprintf("Unpack pod(%s/%s) container(%s) is pending. Reason: %s, Message: %s \n",
540+
fmt.Sprintf("Unpack pod(%s/%s) container(%s) is pending. Reason: %s, Message: %s | ",
520541
pod.Namespace, pod.Name, ic.Name, ic.State.Waiting.Reason, ic.State.Waiting.Message)
521542
}
522543
}
@@ -538,8 +559,8 @@ func (c *ConfigMapUnpacker) ensureConfigmap(csRef *corev1.ObjectReference, name
538559
return
539560
}
540561

541-
func (c *ConfigMapUnpacker) ensureJob(cmRef *corev1.ObjectReference, bundlePath string, secrets []corev1.LocalObjectReference) (job *batchv1.Job, err error) {
542-
fresh := c.job(cmRef, bundlePath, secrets)
562+
func (c *ConfigMapUnpacker) ensureJob(cmRef *corev1.ObjectReference, bundlePath string, secrets []corev1.LocalObjectReference, ipAnnotations map[string]string) (job *batchv1.Job, err error) {
563+
fresh := c.job(cmRef, bundlePath, secrets, ipAnnotations)
543564
job, err = c.jobLister.Jobs(fresh.GetNamespace()).Get(fresh.GetName())
544565
if err != nil {
545566
if apierrors.IsNotFound(err) {

pkg/controller/bundle/bundle_unpacker_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -795,7 +795,7 @@ func TestConfigMapUnpacker(t *testing.T) {
795795
)
796796
require.NoError(t, err)
797797

798-
res, err := unpacker.UnpackBundle(tt.args.lookup)
798+
res, err := unpacker.UnpackBundle(tt.args.lookup, map[string]string{})
799799
require.Equal(t, tt.expected.err, err)
800800

801801
if tt.expected.res == nil {

pkg/controller/bundle/bundlefakes/fake_unpacker.go

Lines changed: 10 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/controller/operators/catalog/operator.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1186,7 +1186,7 @@ func (o *Operator) unpackBundles(plan *v1alpha1.InstallPlan) (bool, *v1alpha1.In
11861186
var errs []error
11871187
for i := 0; i < len(out.Status.BundleLookups); i++ {
11881188
lookup := out.Status.BundleLookups[i]
1189-
res, err := o.bundleUnpacker.UnpackBundle(&lookup)
1189+
res, err := o.bundleUnpacker.UnpackBundle(&lookup, plan.GetAnnotations())
11901190
if err != nil {
11911191
errs = append(errs, err)
11921192
continue

test/e2e/installplan_e2e_test.go

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import (
3939
"github.com/operator-framework/api/pkg/operators/v1alpha1"
4040
operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
4141
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
42+
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/bundle"
4243
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/catalog"
4344
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
4445
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/kubernetes/pkg/apis/rbac"
@@ -3088,6 +3089,14 @@ var _ = Describe("Install Plan", func() {
30883089
// Create an InstallPlan status.bundleLookups.Path specified for a non-existent bundle image
30893090
ip.Status.BundleLookups[0].Path = "quay.io/foo/bar:v0.0.1"
30903091

3092+
// We wait for some time over the bundle unpack timeout (i.e ActiveDeadlineSeconds) so that the Job can eventually fail
3093+
// Since the default --bundle-unpack-timeout=10m, we override with a shorter timeout via the
3094+
// unpack timeout annotation on the InstallPlan
3095+
annotations := make(map[string]string)
3096+
annotations[bundle.BundleUnpackTimeoutAnnotationKey] = "1m"
3097+
ip.SetAnnotations(annotations)
3098+
waitFor := 1*time.Minute + 30*time.Second
3099+
30913100
outIP := ip.DeepCopy()
30923101

30933102
Expect(ctx.Ctx().Client().Create(context.Background(), outIP)).To(Succeed())
@@ -3118,6 +3127,15 @@ var _ = Describe("Install Plan", func() {
31183127
},
31193128
1*time.Minute,
31203129
).Should(ContainSubstring("ErrImagePull"))
3130+
3131+
// The InstallPlan should eventually fail due to the ActiveDeadlineSeconds limit
3132+
Eventually(
3133+
func() (*operatorsv1alpha1.InstallPlan, error) {
3134+
err := ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(outIP), outIP)
3135+
return outIP, err
3136+
},
3137+
waitFor,
3138+
).Should(HavePhase(operatorsv1alpha1.InstallPlanPhaseFailed))
31213139
})
31223140

31233141
It("should timeout and fail the InstallPlan for an invalid bundle image", func() {
@@ -3133,16 +3151,15 @@ var _ = Describe("Install Plan", func() {
31333151
outIP.Status = ip.Status
31343152
Expect(ctx.Ctx().Client().Status().Update(context.Background(), outIP)).To(Succeed())
31353153

3136-
// The InstallPlan should be Failed due to the bundle unpack job timeout
3137-
// We wait for some time over the default --bundle-unpack-timeout (i.e ActiveDeadlineSeconds) so that the Job can fail
3138-
defaultBundleUnpackTimeout := 10 * time.Minute
3139-
waitFor := defaultBundleUnpackTimeout + 30*time.Second
3154+
// The InstallPlan should fail after the unpack pod keeps failing and exceeds the job's
3155+
// BackoffLimit(set to 3), which with an exponential backoff (10s + 20s + 40s)= 1m10s
3156+
// so we wait a little over that.
31403157
Eventually(
31413158
func() (*operatorsv1alpha1.InstallPlan, error) {
31423159
err := ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(outIP), outIP)
31433160
return outIP, err
31443161
},
3145-
waitFor,
3162+
2*time.Minute,
31463163
).Should(HavePhase(operatorsv1alpha1.InstallPlanPhaseFailed))
31473164
})
31483165

0 commit comments

Comments
 (0)