@@ -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"
@@ -80,7 +81,26 @@ func (r *SandboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
8081 }
8182
8283 oldStatus := sandbox .Status .DeepCopy ()
84+ var err error
8385
86+ expired , requeueAfter := r .processSandboxExpiry (sandbox )
87+
88+ // Check if sandbox has expired
89+ if expired {
90+ log .Info ("Sandbox has expired, deleting pod and service" )
91+ err = r .deleteChildResources (ctx , sandbox )
92+ } else {
93+ err = r .reconcileChildResources (ctx , sandbox )
94+ }
95+
96+ // Update status
97+ err = errors .Join (err , r .updateStatus (ctx , oldStatus , sandbox ))
98+
99+ // return errors seen
100+ return ctrl.Result {RequeueAfter : requeueAfter }, err
101+ }
102+
103+ func (r * SandboxReconciler ) reconcileChildResources (ctx context.Context , sandbox * sandboxv1alpha1.Sandbox ) error {
84104 // Create a hash from the sandbox.Name and use it as label value
85105 nameHash := NameHash (sandbox .Name )
86106
@@ -98,12 +118,7 @@ func (r *SandboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
98118 readyCondition := r .computeReadyCondition (sandbox .Generation , allErrors , svc , pod )
99119 meta .SetStatusCondition (& sandbox .Status .Conditions , readyCondition )
100120
101- // Update status
102- err = r .updateStatus (ctx , oldStatus , sandbox )
103- allErrors = errors .Join (allErrors , err )
104-
105- // return errors seen
106- return ctrl.Result {}, allErrors
121+ return allErrors
107122}
108123
109124func (r * SandboxReconciler ) computeReadyCondition (generation int64 , err error , svc * corev1.Service , pod * corev1.Pod ) metav1.Condition {
@@ -280,6 +295,61 @@ func (r *SandboxReconciler) reconcilePod(ctx context.Context, sandbox *sandboxv1
280295 return pod , nil
281296}
282297
298+ func (r * SandboxReconciler ) deleteChildResources (ctx context.Context , sandbox * sandboxv1alpha1.Sandbox ) error {
299+ var allErrors error
300+ pod := & corev1.Pod {
301+ ObjectMeta : metav1.ObjectMeta {
302+ Name : sandbox .Name ,
303+ Namespace : sandbox .Namespace ,
304+ },
305+ }
306+ if err := r .Delete (ctx , pod ); err != nil && ! k8serrors .IsNotFound (err ) {
307+ allErrors = errors .Join (allErrors , fmt .Errorf ("failed to delete pod: %w" , err ))
308+ }
309+
310+ service := & corev1.Service {
311+ ObjectMeta : metav1.ObjectMeta {
312+ Name : sandbox .Name ,
313+ Namespace : sandbox .Namespace ,
314+ },
315+ }
316+ if err := r .Delete (ctx , service ); err != nil && ! k8serrors .IsNotFound (err ) {
317+ allErrors = errors .Join (allErrors , fmt .Errorf ("failed to delete service: %w" , err ))
318+ }
319+
320+ // Update status to remove Ready condition
321+ meta .SetStatusCondition (& sandbox .Status .Conditions , metav1.Condition {
322+ Type : string (sandboxv1alpha1 .SandboxConditionReady ),
323+ Status : metav1 .ConditionFalse ,
324+ ObservedGeneration : sandbox .Generation ,
325+ Reason : "SandboxExpired" ,
326+ Message : "Sandbox has expired" ,
327+ })
328+
329+ return allErrors
330+ }
331+
332+ func (r * SandboxReconciler ) processSandboxExpiry (sandbox * sandboxv1alpha1.Sandbox ) (bool , time.Duration ) {
333+ if sandbox .Spec .ShutdownTime == nil {
334+ return false , 0
335+ }
336+
337+ expiryTime := sandbox .Spec .ShutdownTime .Time
338+ if time .Now ().After (expiryTime ) {
339+ return true , 0
340+ }
341+
342+ // Calculate remaining time
343+ remainingTime := time .Until (expiryTime )
344+
345+ // TODO(barney-s): Do we need a inverse exponential backoff here ?
346+ //requeueAfter := max(remainingTime/2, 2*time.Second)
347+
348+ // Requeue at expiry time or in 2 seconds whichever is later
349+ requeueAfter := max (remainingTime , 2 * time .Second )
350+ return false , requeueAfter
351+ }
352+
283353// SetupWithManager sets up the controller with the Manager.
284354func (r * SandboxReconciler ) SetupWithManager (mgr ctrl.Manager ) error {
285355 labelSelectorPredicate , err := predicate .LabelSelectorPredicate (metav1.LabelSelector {
0 commit comments