@@ -31,6 +31,8 @@ import (
31
31
"github.com/robfig/cron"
32
32
kbatch "k8s.io/api/batch/v1"
33
33
corev1 "k8s.io/api/core/v1"
34
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
35
+ "k8s.io/apimachinery/pkg/api/meta"
34
36
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
35
37
"k8s.io/apimachinery/pkg/runtime"
36
38
ref "k8s.io/client-go/tools/reference"
@@ -68,6 +70,16 @@ type Clock interface {
68
70
69
71
// +kubebuilder:docs-gen:collapse=Clock
70
72
73
+ // Definitions to manage status conditions
74
+ const (
75
+ // typeAvailableCronJob represents the status of the CronJob reconciliation
76
+ typeAvailableCronJob = "Available"
77
+ // typeProgressingCronJob represents the status used when the CronJob is being reconciled
78
+ typeProgressingCronJob = "Progressing"
79
+ // typeDegradedCronJob represents the status used when the CronJob has encountered an error
80
+ typeDegradedCronJob = "Degraded"
81
+ )
82
+
71
83
/*
72
84
Notice that we need a few more RBAC permissions -- since we're creating and
73
85
managing jobs now, we'll need permissions for those, which means adding
@@ -114,11 +126,35 @@ func (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
114
126
*/
115
127
var cronJob batchv1.CronJob
116
128
if err := r .Get (ctx , req .NamespacedName , & cronJob ); err != nil {
117
- log .Error (err , "unable to fetch CronJob" )
118
- // we'll ignore not-found errors, since they can't be fixed by an immediate
119
- // requeue (we'll need to wait for a new notification), and we can get them
120
- // on deleted requests.
121
- return ctrl.Result {}, client .IgnoreNotFound (err )
129
+ if apierrors .IsNotFound (err ) {
130
+ // If the custom resource is not found then it usually means that it was deleted or not created
131
+ // In this way, we will stop the reconciliation
132
+ log .Info ("CronJob resource not found. Ignoring since object must be deleted" )
133
+ return ctrl.Result {}, nil
134
+ }
135
+ // Error reading the object - requeue the request.
136
+ log .Error (err , "Failed to get CronJob" )
137
+ return ctrl.Result {}, err
138
+ }
139
+
140
+ // Initialize status conditions if not yet present
141
+ if len (cronJob .Status .Conditions ) == 0 {
142
+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
143
+ Type : typeProgressingCronJob ,
144
+ Status : metav1 .ConditionUnknown ,
145
+ Reason : "Reconciling" ,
146
+ Message : "Starting reconciliation" ,
147
+ })
148
+ if err := r .Status ().Update (ctx , & cronJob ); err != nil {
149
+ log .Error (err , "Failed to update CronJob status" )
150
+ return ctrl.Result {}, err
151
+ }
152
+
153
+ // Re-fetch the CronJob after updating the status
154
+ if err := r .Get (ctx , req .NamespacedName , & cronJob ); err != nil {
155
+ log .Error (err , "Failed to re-fetch CronJob" )
156
+ return ctrl.Result {}, err
157
+ }
122
158
}
123
159
124
160
/*
@@ -131,6 +167,16 @@ func (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
131
167
var childJobs kbatch.JobList
132
168
if err := r .List (ctx , & childJobs , client .InNamespace (req .Namespace ), client.MatchingFields {jobOwnerKey : req .Name }); err != nil {
133
169
log .Error (err , "unable to list child Jobs" )
170
+ // Update status condition to reflect the error
171
+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
172
+ Type : typeDegradedCronJob ,
173
+ Status : metav1 .ConditionTrue ,
174
+ Reason : "ReconciliationError" ,
175
+ Message : fmt .Sprintf ("Failed to list child jobs: %v" , err ),
176
+ })
177
+ if statusErr := r .Status ().Update (ctx , & cronJob ); statusErr != nil {
178
+ log .Error (statusErr , "Failed to update CronJob status" )
179
+ }
134
180
return ctrl.Result {}, err
135
181
}
136
182
@@ -247,6 +293,58 @@ func (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
247
293
*/
248
294
log .V (1 ).Info ("job count" , "active jobs" , len (activeJobs ), "successful jobs" , len (successfulJobs ), "failed jobs" , len (failedJobs ))
249
295
296
+ // Check if CronJob is suspended
297
+ isSuspended := cronJob .Spec .Suspend != nil && * cronJob .Spec .Suspend
298
+
299
+ // Update status conditions based on current state
300
+ if isSuspended {
301
+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
302
+ Type : typeAvailableCronJob ,
303
+ Status : metav1 .ConditionFalse ,
304
+ Reason : "Suspended" ,
305
+ Message : "CronJob is suspended" ,
306
+ })
307
+ } else if len (failedJobs ) > 0 {
308
+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
309
+ Type : typeDegradedCronJob ,
310
+ Status : metav1 .ConditionTrue ,
311
+ Reason : "JobsFailed" ,
312
+ Message : fmt .Sprintf ("%d job(s) have failed" , len (failedJobs )),
313
+ })
314
+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
315
+ Type : typeAvailableCronJob ,
316
+ Status : metav1 .ConditionFalse ,
317
+ Reason : "JobsFailed" ,
318
+ Message : fmt .Sprintf ("%d job(s) have failed" , len (failedJobs )),
319
+ })
320
+ } else if len (activeJobs ) > 0 {
321
+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
322
+ Type : typeProgressingCronJob ,
323
+ Status : metav1 .ConditionTrue ,
324
+ Reason : "JobsActive" ,
325
+ Message : fmt .Sprintf ("%d job(s) are currently active" , len (activeJobs )),
326
+ })
327
+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
328
+ Type : typeAvailableCronJob ,
329
+ Status : metav1 .ConditionTrue ,
330
+ Reason : "JobsActive" ,
331
+ Message : fmt .Sprintf ("CronJob is progressing with %d active job(s)" , len (activeJobs )),
332
+ })
333
+ } else {
334
+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
335
+ Type : typeAvailableCronJob ,
336
+ Status : metav1 .ConditionTrue ,
337
+ Reason : "AllJobsCompleted" ,
338
+ Message : "All jobs have completed successfully" ,
339
+ })
340
+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
341
+ Type : typeProgressingCronJob ,
342
+ Status : metav1 .ConditionFalse ,
343
+ Reason : "NoJobsActive" ,
344
+ Message : "No jobs are currently active" ,
345
+ })
346
+ }
347
+
250
348
/*
251
349
Using the data we've gathered, we'll update the status of our CRD.
252
350
Just like before, we use our client. To specifically update the status
@@ -400,6 +498,16 @@ func (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
400
498
missedRun , nextRun , err := getNextSchedule (& cronJob , r .Now ())
401
499
if err != nil {
402
500
log .Error (err , "unable to figure out CronJob schedule" )
501
+ // Update status condition to reflect the schedule error
502
+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
503
+ Type : typeDegradedCronJob ,
504
+ Status : metav1 .ConditionTrue ,
505
+ Reason : "InvalidSchedule" ,
506
+ Message : fmt .Sprintf ("Failed to parse schedule: %v" , err ),
507
+ })
508
+ if statusErr := r .Status ().Update (ctx , & cronJob ); statusErr != nil {
509
+ log .Error (statusErr , "Failed to update CronJob status" )
510
+ }
403
511
// we don't really care about requeuing until we get an update that
404
512
// fixes the schedule, so don't return an error
405
513
return ctrl.Result {}, nil
@@ -430,7 +538,16 @@ func (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
430
538
}
431
539
if tooLate {
432
540
log .V (1 ).Info ("missed starting deadline for last run, sleeping till next" )
433
- // TODO(directxman12): events
541
+ // Update status condition to reflect missed deadline
542
+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
543
+ Type : typeDegradedCronJob ,
544
+ Status : metav1 .ConditionTrue ,
545
+ Reason : "MissedSchedule" ,
546
+ Message : fmt .Sprintf ("Missed starting deadline for run at %v" , missedRun ),
547
+ })
548
+ if statusErr := r .Status ().Update (ctx , & cronJob ); statusErr != nil {
549
+ log .Error (statusErr , "Failed to update CronJob status" )
550
+ }
434
551
return scheduledResult , nil
435
552
}
436
553
@@ -511,11 +628,32 @@ func (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
511
628
// ...and create it on the cluster
512
629
if err := r .Create (ctx , job ); err != nil {
513
630
log .Error (err , "unable to create Job for CronJob" , "job" , job )
631
+ // Update status condition to reflect the error
632
+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
633
+ Type : typeDegradedCronJob ,
634
+ Status : metav1 .ConditionTrue ,
635
+ Reason : "JobCreationFailed" ,
636
+ Message : fmt .Sprintf ("Failed to create job: %v" , err ),
637
+ })
638
+ if statusErr := r .Status ().Update (ctx , & cronJob ); statusErr != nil {
639
+ log .Error (statusErr , "Failed to update CronJob status" )
640
+ }
514
641
return ctrl.Result {}, err
515
642
}
516
643
517
644
log .V (1 ).Info ("created Job for CronJob run" , "job" , job )
518
645
646
+ // Update status condition to reflect successful job creation
647
+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
648
+ Type : typeProgressingCronJob ,
649
+ Status : metav1 .ConditionTrue ,
650
+ Reason : "JobCreated" ,
651
+ Message : fmt .Sprintf ("Created job %s" , job .Name ),
652
+ })
653
+ if statusErr := r .Status ().Update (ctx , & cronJob ); statusErr != nil {
654
+ log .Error (statusErr , "Failed to update CronJob status" )
655
+ }
656
+
519
657
/*
520
658
### 7: Requeue when we either see a running job or it's time for the next scheduled run
521
659
0 commit comments