@@ -401,6 +401,104 @@ var _ = Describe("ImpVM Agent: Reconcile non-existent VM (NotFound)", func() {
401401 })
402402})
403403
404+ var _ = Describe ("ImpVM Agent: Running — lazy reattach on agent restart" , func () {
405+ ctx := context .Background ()
406+
407+ It ("reattaches and stays Running when PID is alive" , func () {
408+ driver := NewStubDriver ()
409+ driver .IsAliveResult = true
410+
411+ vm := & impdevv1alpha1.ImpVM {
412+ ObjectMeta : metav1.ObjectMeta {
413+ Name : "tc-reattach-alive" , Namespace : "default" ,
414+ Finalizers : []string {"imp/finalizer" },
415+ },
416+ Spec : impdevv1alpha1.ImpVMSpec {NodeName : testNode },
417+ }
418+ Expect (k8sClient .Create (ctx , vm )).To (Succeed ())
419+ DeferCleanup (func () { k8sClient .Delete (ctx , vm ) }) //nolint:errcheck
420+
421+ base := vm .DeepCopy ()
422+ vm .Status .Phase = impdevv1alpha1 .VMPhaseRunning
423+ vm .Status .RuntimePID = 99999
424+ Expect (k8sClient .Status ().Patch (ctx , vm , client .MergeFrom (base ))).To (Succeed ())
425+
426+ // Inspect returns Running=false (no entry in states map — simulates restart)
427+ // IsAliveResult=true means the PID check passes
428+
429+ _ , err := newReconciler (driver ).Reconcile (ctx , reconcile.Request {
430+ NamespacedName : types.NamespacedName {Name : "tc-reattach-alive" , Namespace : "default" },
431+ })
432+ Expect (err ).NotTo (HaveOccurred ())
433+
434+ updated := & impdevv1alpha1.ImpVM {}
435+ Expect (k8sClient .Get (ctx , types.NamespacedName {Name : "tc-reattach-alive" , Namespace : "default" }, updated )).To (Succeed ())
436+ Expect (updated .Status .Phase ).To (Equal (impdevv1alpha1 .VMPhaseRunning ))
437+ Expect (driver .ReattachCalls ).To (HaveLen (1 ))
438+ Expect (driver .ReattachCalls [0 ]).To (Equal ("default/tc-reattach-alive" ))
439+ })
440+
441+ It ("transitions to Failed when PID is dead" , func () {
442+ driver := NewStubDriver ()
443+ driver .IsAliveResult = false
444+
445+ vm := & impdevv1alpha1.ImpVM {
446+ ObjectMeta : metav1.ObjectMeta {
447+ Name : "tc-reattach-dead" , Namespace : "default" ,
448+ Finalizers : []string {"imp/finalizer" },
449+ },
450+ Spec : impdevv1alpha1.ImpVMSpec {NodeName : testNode },
451+ }
452+ Expect (k8sClient .Create (ctx , vm )).To (Succeed ())
453+ DeferCleanup (func () { k8sClient .Delete (ctx , vm ) }) //nolint:errcheck
454+
455+ base := vm .DeepCopy ()
456+ vm .Status .Phase = impdevv1alpha1 .VMPhaseRunning
457+ vm .Status .RuntimePID = 99999
458+ Expect (k8sClient .Status ().Patch (ctx , vm , client .MergeFrom (base ))).To (Succeed ())
459+
460+ _ , err := newReconciler (driver ).Reconcile (ctx , reconcile.Request {
461+ NamespacedName : types.NamespacedName {Name : "tc-reattach-dead" , Namespace : "default" },
462+ })
463+ Expect (err ).NotTo (HaveOccurred ())
464+
465+ updated := & impdevv1alpha1.ImpVM {}
466+ Expect (k8sClient .Get (ctx , types.NamespacedName {Name : "tc-reattach-dead" , Namespace : "default" }, updated )).To (Succeed ())
467+ Expect (updated .Status .Phase ).To (Equal (impdevv1alpha1 .VMPhaseFailed ))
468+ Expect (driver .ReattachCalls ).To (BeEmpty ())
469+ })
470+
471+ It ("transitions to Failed when RuntimePID is zero" , func () {
472+ driver := NewStubDriver ()
473+ driver .IsAliveResult = true // shouldn't matter — PID is 0
474+
475+ vm := & impdevv1alpha1.ImpVM {
476+ ObjectMeta : metav1.ObjectMeta {
477+ Name : "tc-reattach-nopid" , Namespace : "default" ,
478+ Finalizers : []string {"imp/finalizer" },
479+ },
480+ Spec : impdevv1alpha1.ImpVMSpec {NodeName : testNode },
481+ }
482+ Expect (k8sClient .Create (ctx , vm )).To (Succeed ())
483+ DeferCleanup (func () { k8sClient .Delete (ctx , vm ) }) //nolint:errcheck
484+
485+ base := vm .DeepCopy ()
486+ vm .Status .Phase = impdevv1alpha1 .VMPhaseRunning
487+ vm .Status .RuntimePID = 0
488+ Expect (k8sClient .Status ().Patch (ctx , vm , client .MergeFrom (base ))).To (Succeed ())
489+
490+ _ , err := newReconciler (driver ).Reconcile (ctx , reconcile.Request {
491+ NamespacedName : types.NamespacedName {Name : "tc-reattach-nopid" , Namespace : "default" },
492+ })
493+ Expect (err ).NotTo (HaveOccurred ())
494+
495+ updated := & impdevv1alpha1.ImpVM {}
496+ Expect (k8sClient .Get (ctx , types.NamespacedName {Name : "tc-reattach-nopid" , Namespace : "default" }, updated )).To (Succeed ())
497+ Expect (updated .Status .Phase ).To (Equal (impdevv1alpha1 .VMPhaseFailed ))
498+ Expect (driver .ReattachCalls ).To (BeEmpty ())
499+ })
500+ })
501+
404502var _ = Describe ("ImpVM Agent: handleTerminating driver.Stop error" , func () {
405503 ctx := context .Background ()
406504
0 commit comments