@@ -12,9 +12,11 @@ import (
1212 "go.opentelemetry.io/otel"
1313 "go.opentelemetry.io/otel/attribute"
1414 "go.opentelemetry.io/otel/trace"
15+ corev1 "k8s.io/api/core/v1"
1516 apierrors "k8s.io/apimachinery/pkg/api/errors"
1617 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1718 "k8s.io/apimachinery/pkg/runtime"
19+ "k8s.io/client-go/tools/record"
1820 "k8s.io/client-go/util/retry"
1921 ctrl "sigs.k8s.io/controller-runtime"
2022 "sigs.k8s.io/controller-runtime/pkg/client"
@@ -44,6 +46,8 @@ type ImpVMReconciler struct {
4446 // StartTimeout is how long a VM may remain in Starting before being
4547 // transitioned to Failed. Defaults to 5 minutes when zero.
4648 StartTimeout time.Duration
49+ // Recorder emits lifecycle events for VM completion/failure.
50+ Recorder record.EventRecorder
4751}
4852
4953// +kubebuilder:rbac:groups=imp.dev,resources=impvms,verbs=get;list;watch;update;patch
@@ -266,8 +270,9 @@ func (r *ImpVMReconciler) handleRunning(ctx context.Context, vm *impdevv1alpha1.
266270 }
267271 }
268272
269- log .Info ("VM process exited" , "lifecycle" , vm .Spec .Lifecycle )
270- if vm .Spec .Lifecycle == impdevv1alpha1 .VMLifecycleEphemeral {
273+ lifecycle := vmLifecycleOrDefault (vm )
274+ log .Info ("VM process exited" , "lifecycle" , lifecycle )
275+ if lifecycle == impdevv1alpha1 .VMLifecycleEphemeral {
271276 return r .finishSucceeded (ctx , vm )
272277 }
273278 return r .finishFailed (ctx , vm )
@@ -317,11 +322,15 @@ func (r *ImpVMReconciler) finishSucceeded(ctx context.Context, vm *impdevv1alpha
317322 // Status patch — take base AFTER spec patch so resourceVersion is current.
318323 base := vm .DeepCopy ()
319324 vm .Status .Phase = impdevv1alpha1 .VMPhaseSucceeded
325+ vm .Status .StartedAt = nil
320326 vm .Status .IP = ""
321327 vm .Status .RuntimePID = 0
322328 if err := r .Status ().Patch (ctx , vm , client .MergeFrom (base )); err != nil {
323329 return ctrl.Result {}, err
324330 }
331+ if r .Recorder != nil {
332+ r .Recorder .Event (vm , corev1 .EventTypeNormal , "Completed" , "VM exited and was marked Succeeded" )
333+ }
325334 return ctrl.Result {}, nil
326335}
327336
@@ -333,9 +342,19 @@ func (r *ImpVMReconciler) finishFailed(ctx context.Context, vm *impdevv1alpha1.I
333342 if err := r .Status ().Patch (ctx , vm , client .MergeFrom (base )); err != nil {
334343 return ctrl.Result {}, err
335344 }
345+ if r .Recorder != nil {
346+ r .Recorder .Event (vm , corev1 .EventTypeWarning , "ProcessExited" , "VM process exited and was marked Failed" )
347+ }
336348 return ctrl.Result {}, nil
337349}
338350
351+ func vmLifecycleOrDefault (vm * impdevv1alpha1.ImpVM ) impdevv1alpha1.VMLifecycle {
352+ if vm .Spec .Lifecycle == "" {
353+ return impdevv1alpha1 .VMLifecycleEphemeral
354+ }
355+ return vm .Spec .Lifecycle
356+ }
357+
339358// clearOwnership clears spec.nodeName + status ip/pid after Terminating stop.
340359func (r * ImpVMReconciler ) clearOwnership (ctx context.Context , vm * impdevv1alpha1.ImpVM ) (ctrl.Result , error ) {
341360 specBase := vm .DeepCopy ()
0 commit comments