@@ -10,6 +10,7 @@ import (
1010 "github.com/platform9/vjailbreak/pkg/common/constants"
1111 "github.com/platform9/vjailbreak/v2v-helper/nbd"
1212 "github.com/platform9/vjailbreak/v2v-helper/openstack"
13+ "github.com/platform9/vjailbreak/v2v-helper/pkg/k8sutils"
1314 "github.com/platform9/vjailbreak/v2v-helper/vm"
1415
1516 "github.com/golang/mock/gomock"
@@ -517,7 +518,7 @@ func TestCreateTargetInstance(t *testing.T) {
517518 mockOpenStackOps .EXPECT ().CreatePort (gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any ()).Return (& ports.Port {
518519 MACAddress : "mac-address" ,
519520 }, nil ).AnyTimes ()
520- mockOpenStackOps .EXPECT ().CreateVM (gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any ()).Return (& servers.Server {}, nil ).AnyTimes ()
521+ mockOpenStackOps .EXPECT ().CreateVM (gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock . Any () ).Return (& servers.Server {}, nil ).AnyTimes ()
521522 mockOpenStackOps .EXPECT ().WaitUntilVMActive (gomock .Any (), gomock .Any ()).Return (true , nil ).AnyTimes ()
522523 mockOpenStackOps .EXPECT ().GetFlavor (gomock .Any (), "flavor-id" ).Return (& flavors.Flavor {
523524 VCPUs : 2 ,
@@ -571,7 +572,7 @@ func TestCreateTargetInstance_AdvancedMapping_Ports(t *testing.T) {
571572 ID : "port-2-id" ,
572573 NetworkID : "network-2" ,
573574 }, nil ).AnyTimes ()
574- mockOpenStackOps .EXPECT ().CreateVM (gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any ()).Return (& servers.Server {}, nil ).AnyTimes ()
575+ mockOpenStackOps .EXPECT ().CreateVM (gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock . Any () ).Return (& servers.Server {}, nil ).AnyTimes ()
575576 mockOpenStackOps .EXPECT ().WaitUntilVMActive (gomock .Any (), gomock .Any ()).Return (true , nil ).AnyTimes ()
576577 mockOpenStackOps .EXPECT ().GetFlavor (gomock .Any (), "flavor-id" ).Return (& flavors.Flavor {
577578 VCPUs : 2 ,
@@ -619,7 +620,7 @@ func TestCreateTargetInstance_AdvancedMapping_InsufficientPorts(t *testing.T) {
619620 RAM : 2048 ,
620621 }, nil ).AnyTimes ()
621622 mockOpenStackOps .EXPECT ().GetSecurityGroupIDs (gomock .Any (), gomock .Any (), gomock .Any ()).Return ([]string {}, nil ).AnyTimes ()
622- mockOpenStackOps .EXPECT ().CreateVM (gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any ()).Return (& servers.Server {}, nil ).AnyTimes ()
623+ mockOpenStackOps .EXPECT ().CreateVM (gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock . Any () ).Return (& servers.Server {}, nil ).AnyTimes ()
623624 mockOpenStackOps .EXPECT ().WaitUntilVMActive (gomock .Any (), gomock .Any ()).Return (true , nil ).AnyTimes ()
624625 inputvminfo := vm.VMInfo {
625626 Name : "test-vm" ,
@@ -652,3 +653,80 @@ func TestCreateTargetInstance_AdvancedMapping_InsufficientPorts(t *testing.T) {
652653 // This test now just verifies that CreateTargetInstance can handle mismatched Networkports config
653654 assert .NoError (t , err )
654655}
656+
657+ func TestDetachAllVolumes_SkipsNilOpenstackVol (t * testing.T ) {
658+ ctrl := gomock .NewController (t )
659+ defer ctrl .Finish ()
660+ ctx := context .Background ()
661+
662+ mockOpenStackOps := openstack .NewMockOpenstackOperations (ctrl )
663+ // Only disk1 has a volume — expect exactly one detach+wait, not two.
664+ mockOpenStackOps .EXPECT ().DetachVolumeFromVM (gomock .Any (), "id1" ).Return (nil ).Times (1 )
665+ mockOpenStackOps .EXPECT ().WaitForVolume (gomock .Any (), "id1" ).Return (nil ).Times (1 )
666+
667+ vminfo := vm.VMInfo {
668+ VMDisks : []vm.VMDisk {
669+ {Name : "disk1" , OpenstackVol : & volumes.Volume {ID : "id1" }},
670+ {Name : "disk2" , OpenstackVol : nil },
671+ },
672+ }
673+ migobj := Migrate {Openstackclients : mockOpenStackOps , InPod : false }
674+ err := migobj .DetachAllVolumes (ctx , vminfo )
675+ assert .NoError (t , err )
676+ }
677+
678+ func TestDeleteAllVolumes_SkipsNilOpenstackVol (t * testing.T ) {
679+ ctrl := gomock .NewController (t )
680+ defer ctrl .Finish ()
681+ ctx := context .Background ()
682+
683+ mockOpenStackOps := openstack .NewMockOpenstackOperations (ctrl )
684+ // Only disk1 has a volume — expect exactly one delete call.
685+ mockOpenStackOps .EXPECT ().DeleteVolume (gomock .Any (), "id1" ).Return (nil ).Times (1 )
686+
687+ vminfo := vm.VMInfo {
688+ VMDisks : []vm.VMDisk {
689+ {Name : "disk1" , OpenstackVol : & volumes.Volume {ID : "id1" }},
690+ {Name : "disk2" , OpenstackVol : nil },
691+ },
692+ }
693+ migobj := Migrate {Openstackclients : mockOpenStackOps , InPod : false }
694+ err := migobj .DeleteAllVolumes (ctx , vminfo )
695+ assert .NoError (t , err )
696+ }
697+
698+ // TestCleanup_PartialVolumes verifies that when CreateVolumes fails partway through,
699+ // cleanup correctly handles the mix of created (non-nil) and uncreated (nil) volumes,
700+ // deletes only the created volume, and deletes ports when the setting is enabled.
701+ func TestCleanup_PartialVolumes_DeletesCreatedVolumeAndPorts (t * testing.T ) {
702+ ctrl := gomock .NewController (t )
703+ defer ctrl .Finish ()
704+ ctx := context .Background ()
705+
706+ mockOpenStackOps := openstack .NewMockOpenstackOperations (ctrl )
707+ mockVMOps := vm .NewMockVMOperations (ctrl )
708+
709+ // disk1 was created; disk2 was never created (nil OpenstackVol).
710+ mockOpenStackOps .EXPECT ().DetachVolumeFromVM (gomock .Any (), "id1" ).Return (nil ).Times (1 )
711+ mockOpenStackOps .EXPECT ().WaitForVolume (gomock .Any (), "id1" ).Return (nil ).Times (1 )
712+ mockOpenStackOps .EXPECT ().DeleteVolume (gomock .Any (), "id1" ).Return (nil ).Times (1 )
713+ mockOpenStackOps .EXPECT ().DeletePort (gomock .Any (), "port-1" ).Return (nil ).Times (1 )
714+ mockVMOps .EXPECT ().CleanUpSnapshots (true ).Return (nil ).Times (1 )
715+
716+ vminfo := vm.VMInfo {
717+ VMDisks : []vm.VMDisk {
718+ {Name : "disk1" , OpenstackVol : & volumes.Volume {ID : "id1" }},
719+ {Name : "disk2" , OpenstackVol : nil },
720+ },
721+ }
722+ settings := & k8sutils.VjailbreakSettings {
723+ CleanupPortsAfterMigrationFailure : true ,
724+ }
725+ migobj := Migrate {
726+ Openstackclients : mockOpenStackOps ,
727+ VMops : mockVMOps ,
728+ InPod : false ,
729+ }
730+ err := migobj .cleanup (ctx , vminfo , "test partial volume failure" , []string {"port-1" }, settings )
731+ assert .NoError (t , err )
732+ }
0 commit comments