@@ -41,6 +41,13 @@ const (
41
41
// e.g 1m30s
42
42
BundleUnpackTimeoutAnnotationKey = "operatorframework.io/bundle-unpack-timeout"
43
43
BundleUnpackPodLabel = "job-name"
44
+
45
+ // BundleUnpackRetryMinimumIntervalAnnotationKey sets a minimum interval to wait before
46
+ // attempting to recreate a failed unpack job for a bundle.
47
+ BundleUnpackRetryMinimumIntervalAnnotationKey = "operatorframework.io/bundle-unpack-min-retry-interval"
48
+
49
+ // bundleUnpackRefLabel is used to filter for all unpack jobs for a specific bundle.
50
+ bundleUnpackRefLabel = "operatorframework.io/bundle-unpack-ref"
44
51
)
45
52
46
53
type BundleUnpackResult struct {
@@ -247,6 +254,7 @@ func (c *ConfigMapUnpacker) job(cmRef *corev1.ObjectReference, bundlePath string
247
254
}
248
255
job .SetNamespace (cmRef .Namespace )
249
256
job .SetName (cmRef .Name )
257
+ job .SetLabels (map [string ]string {bundleUnpackRefLabel : cmRef .Name })
250
258
job .SetOwnerReferences ([]metav1.OwnerReference {ownerRef (cmRef )})
251
259
if c .runAsUser > 0 {
252
260
job .Spec .Template .Spec .SecurityContext .RunAsUser = & c .runAsUser
@@ -287,7 +295,7 @@ func (c *ConfigMapUnpacker) job(cmRef *corev1.ObjectReference, bundlePath string
287
295
//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . Unpacker
288
296
289
297
type Unpacker interface {
290
- UnpackBundle (lookup * operatorsv1alpha1.BundleLookup , timeout time.Duration ) (result * BundleUnpackResult , err error )
298
+ UnpackBundle (lookup * operatorsv1alpha1.BundleLookup , timeout , retryInterval time.Duration ) (result * BundleUnpackResult , err error )
291
299
}
292
300
293
301
type ConfigMapUnpacker struct {
@@ -448,7 +456,7 @@ const (
448
456
NotUnpackedMessage = "bundle contents have not yet been persisted to installplan status"
449
457
)
450
458
451
- func (c * ConfigMapUnpacker ) UnpackBundle (lookup * operatorsv1alpha1.BundleLookup , timeout time.Duration ) (result * BundleUnpackResult , err error ) {
459
+ func (c * ConfigMapUnpacker ) UnpackBundle (lookup * operatorsv1alpha1.BundleLookup , timeout , retryInterval time.Duration ) (result * BundleUnpackResult , err error ) {
452
460
result = newBundleUnpackResult (lookup )
453
461
454
462
// if bundle lookup failed condition already present, then there is nothing more to do
@@ -510,7 +518,7 @@ func (c *ConfigMapUnpacker) UnpackBundle(lookup *operatorsv1alpha1.BundleLookup,
510
518
secrets = append (secrets , corev1.LocalObjectReference {Name : secretName })
511
519
}
512
520
var job * batchv1.Job
513
- job , err = c .ensureJob (cmRef , result .Path , secrets , timeout )
521
+ job , err = c .ensureJob (cmRef , result .Path , secrets , timeout , retryInterval )
514
522
if err != nil || job == nil {
515
523
// ensureJob can return nil if the job present does not match the expected job (spec and ownerefs)
516
524
// The current job is deleted in that case so UnpackBundle needs to be retried
@@ -649,7 +657,7 @@ func (c *ConfigMapUnpacker) ensureConfigmap(csRef *corev1.ObjectReference, name
649
657
return
650
658
}
651
659
652
- func (c * ConfigMapUnpacker ) ensureJob (cmRef * corev1.ObjectReference , bundlePath string , secrets []corev1.LocalObjectReference , timeout time.Duration ) (job * batchv1.Job , err error ) {
660
+ func (c * ConfigMapUnpacker ) ensureJob (cmRef * corev1.ObjectReference , bundlePath string , secrets []corev1.LocalObjectReference , timeout time.Duration , unpackRetryInterval time. Duration ) (job * batchv1.Job , err error ) {
653
661
fresh := c .job (cmRef , bundlePath , secrets , timeout )
654
662
job , err = c .jobLister .Jobs (fresh .GetNamespace ()).Get (fresh .GetName ())
655
663
if err != nil {
@@ -659,13 +667,40 @@ func (c *ConfigMapUnpacker) ensureJob(cmRef *corev1.ObjectReference, bundlePath
659
667
660
668
return
661
669
}
662
- // Cleanup old unpacking job and retry
663
- if _ , isFailed := getCondition (job , batchv1 .JobFailed ); isFailed {
664
- err = c .client .BatchV1 ().Jobs (job .GetNamespace ()).Delete (context .TODO (), job .GetName (), metav1.DeleteOptions {})
665
- if err == nil {
666
- job , err = c .client .BatchV1 ().Jobs (fresh .GetNamespace ()).Create (context .TODO (), fresh , metav1.CreateOptions {})
670
+
671
+ // only check for retries if an unpackRetryInterval is specified
672
+ if unpackRetryInterval > 0 {
673
+ if failedCond , isFailed := getCondition (job , batchv1 .JobFailed ); isFailed {
674
+ lastFailureTime := failedCond .LastTransitionTime .Time
675
+ // Look for other unpack jobs for the same bundle
676
+ var jobs []* batchv1.Job
677
+ jobs , err = c .jobLister .Jobs (fresh .GetNamespace ()).List (k8slabels.ValidatedSetSelector {bundleUnpackRefLabel : cmRef .Name })
678
+ if err != nil {
679
+ return
680
+ }
681
+
682
+ var failed bool
683
+ var cond * batchv1.JobCondition
684
+ for _ , j := range jobs {
685
+ cond , failed = getCondition (j , batchv1 .JobFailed )
686
+ if ! failed {
687
+ // found an in-progress unpack attempt
688
+ job = j
689
+ break
690
+ }
691
+ if cond != nil && lastFailureTime .Before (cond .LastTransitionTime .Time ) {
692
+ lastFailureTime = cond .LastTransitionTime .Time
693
+ }
694
+ }
695
+
696
+ if failed {
697
+ if time .Now ().After (lastFailureTime .Add (unpackRetryInterval )) {
698
+ fresh .SetName (fmt .Sprintf ("%s-%d" , fresh .GetName (), len (jobs )))
699
+ job , err = c .client .BatchV1 ().Jobs (fresh .GetNamespace ()).Create (context .TODO (), fresh , metav1.CreateOptions {})
700
+ return
701
+ }
702
+ }
667
703
}
668
- return
669
704
}
670
705
671
706
if equality .Semantic .DeepDerivative (fresh .GetOwnerReferences (), job .GetOwnerReferences ()) && equality .Semantic .DeepDerivative (fresh .Spec , job .Spec ) {
@@ -835,3 +870,28 @@ func OperatorGroupBundleUnpackTimeout(ogLister v1listers.OperatorGroupNamespaceL
835
870
836
871
return d , nil
837
872
}
873
+
874
+ // OperatorGroupBundleUnpackRetryInterval returns bundle unpack retry interval from annotation if specified.
875
+ // If the retry annotation is not set, return retry = 0 which is subsequently ignored. This interval, if > 0,
876
+ // determines the minimum interval between recreating a failed unpack job.
877
+ func OperatorGroupBundleUnpackRetryInterval (ogLister v1listers.OperatorGroupNamespaceLister ) (time.Duration , error ) {
878
+ ogs , err := ogLister .List (k8slabels .Everything ())
879
+ if err != nil {
880
+ return 0 , err
881
+ }
882
+ if len (ogs ) != 1 {
883
+ return 0 , fmt .Errorf ("found %d operatorGroups, expected 1" , len (ogs ))
884
+ }
885
+
886
+ timeoutStr , ok := ogs [0 ].GetAnnotations ()[BundleUnpackRetryMinimumIntervalAnnotationKey ]
887
+ if ! ok {
888
+ return 0 , nil
889
+ }
890
+
891
+ d , err := time .ParseDuration (timeoutStr )
892
+ if err != nil {
893
+ return 0 , fmt .Errorf ("failed to parse unpack timeout annotation(%s: %s): %w" , BundleUnpackRetryMinimumIntervalAnnotationKey , timeoutStr , err )
894
+ }
895
+
896
+ return d , nil
897
+ }
0 commit comments