@@ -20,6 +20,7 @@ import (
2020 "fmt"
2121 "hash/fnv"
2222 "reflect"
23+ "time"
2324
2425 corev1 "k8s.io/api/core/v1"
2526 k8serrors "k8s.io/apimachinery/pkg/api/errors"
@@ -93,7 +94,26 @@ func (r *SandboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
9394 }
9495
9596 oldStatus := sandbox .Status .DeepCopy ()
97+ var err error
9698
99+ expired , requeueAfter := r .processSandboxExpiry (sandbox )
100+
101+ // Check if sandbox has expired
102+ if expired {
103+ log .Info ("Sandbox has expired, deleting pod and service" )
104+ err = r .deleteChildResources (ctx , sandbox )
105+ } else {
106+ err = r .reconcileChildResources (ctx , sandbox )
107+ }
108+
109+ // Update status
110+ err = errors .Join (err , r .updateStatus (ctx , oldStatus , sandbox ))
111+
112+ // return errors seen
113+ return ctrl.Result {RequeueAfter : requeueAfter }, err
114+ }
115+
116+ func (r * SandboxReconciler ) reconcileChildResources (ctx context.Context , sandbox * sandboxv1alpha1.Sandbox ) error {
97117 // Create a hash from the sandbox.Name and use it as label value
98118 nameHash := NameHash (sandbox .Name )
99119
@@ -115,12 +135,7 @@ func (r *SandboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
115135 readyCondition := r .computeReadyCondition (sandbox .Generation , allErrors , svc , pod )
116136 meta .SetStatusCondition (& sandbox .Status .Conditions , readyCondition )
117137
118- // Update status
119- err = r .updateStatus (ctx , oldStatus , sandbox )
120- allErrors = errors .Join (allErrors , err )
121-
122- // return errors seen
123- return ctrl.Result {}, allErrors
138+ return allErrors
124139}
125140
126141func (r * SandboxReconciler ) computeReadyCondition (generation int64 , err error , svc * corev1.Service , pod * corev1.Pod ) metav1.Condition {
@@ -342,6 +357,64 @@ func (r *SandboxReconciler) reconcilePVCs(ctx context.Context, sandbox *sandboxv
342357 return nil
343358}
344359
360+ func (r * SandboxReconciler ) deleteChildResources (ctx context.Context , sandbox * sandboxv1alpha1.Sandbox ) error {
361+ var allErrors error
362+ pod := & corev1.Pod {
363+ ObjectMeta : metav1.ObjectMeta {
364+ Name : sandbox .Name ,
365+ Namespace : sandbox .Namespace ,
366+ },
367+ }
368+ if err := r .Delete (ctx , pod ); err != nil && ! k8serrors .IsNotFound (err ) {
369+ allErrors = errors .Join (allErrors , fmt .Errorf ("failed to delete pod: %w" , err ))
370+ }
371+
372+ service := & corev1.Service {
373+ ObjectMeta : metav1.ObjectMeta {
374+ Name : sandbox .Name ,
375+ Namespace : sandbox .Namespace ,
376+ },
377+ }
378+ if err := r .Delete (ctx , service ); err != nil && ! k8serrors .IsNotFound (err ) {
379+ allErrors = errors .Join (allErrors , fmt .Errorf ("failed to delete service: %w" , err ))
380+ }
381+
382+ // Update status to remove Ready condition
383+ meta .SetStatusCondition (& sandbox .Status .Conditions , metav1.Condition {
384+ Type : string (sandboxv1alpha1 .SandboxConditionReady ),
385+ Status : metav1 .ConditionFalse ,
386+ ObservedGeneration : sandbox .Generation ,
387+ Reason : "SandboxExpired" ,
388+ Message : "Sandbox has expired" ,
389+ })
390+
391+ return allErrors
392+ }
393+
394+ // checks if the sandbox has expired
395+ // returns true if expired, false otherwise
396+ // if not expired, also returns the duration to requeue after
397+ func (r * SandboxReconciler ) processSandboxExpiry (sandbox * sandboxv1alpha1.Sandbox ) (bool , time.Duration ) {
398+ if sandbox .Spec .ShutdownTime == nil {
399+ return false , 0
400+ }
401+
402+ expiryTime := sandbox .Spec .ShutdownTime .Time
403+ if time .Now ().After (expiryTime ) {
404+ return true , 0
405+ }
406+
407+ // Calculate remaining time
408+ remainingTime := time .Until (expiryTime )
409+
410+ // TODO(barney-s): Do we need a inverse exponential backoff here ?
411+ //requeueAfter := max(remainingTime/2, 2*time.Second)
412+
413+ // Requeue at expiry time or in 2 seconds whichever is later
414+ requeueAfter := max (remainingTime , 2 * time .Second )
415+ return false , requeueAfter
416+ }
417+
345418// SetupWithManager sets up the controller with the Manager.
346419func (r * SandboxReconciler ) SetupWithManager (mgr ctrl.Manager ) error {
347420 labelSelectorPredicate , err := predicate .LabelSelectorPredicate (metav1.LabelSelector {
0 commit comments