@@ -844,3 +844,186 @@ func getAllocations(res *v1alpha1.Reservation) map[string]string {
844844 }
845845 return res .Status .FailoverReservation .Allocations
846846}
847+
848+ // ============================================================================
849+ // Test: selectVMsToProcess
850+ // ============================================================================
851+
852+ func TestSelectVMsToProcess (t * testing.T ) {
853+ // Create 10 VMs with different memory sizes (sorted by memory descending)
854+ createVMs := func (count int ) []vmFailoverNeed {
855+ vms := make ([]vmFailoverNeed , count )
856+ for i := range count {
857+ vms [i ] = vmFailoverNeed {
858+ VM : VM {
859+ UUID : "vm-" + string (rune ('a' + i )),
860+ CurrentHypervisor : "host" + string (rune ('1' + i )),
861+ Resources : map [string ]resource.Quantity {
862+ "memory" : * resource .NewQuantity (int64 ((count - i )* 1024 * 1024 * 1024 ), resource .BinarySI ), // Descending memory
863+ },
864+ },
865+ Count : 1 ,
866+ }
867+ }
868+ return vms
869+ }
870+
871+ tests := []struct {
872+ name string
873+ reconcileCount int64
874+ vmCount int
875+ maxToProcess int
876+ expectedOffset int // Expected starting offset in the VM list
877+ expectedHit bool
878+ }{
879+ // 3 out of 4 runs should start at offset 0
880+ {
881+ name : "reconcile 1 - offset 0" ,
882+ reconcileCount : 1 ,
883+ vmCount : 10 ,
884+ maxToProcess : 3 ,
885+ expectedOffset : 0 ,
886+ expectedHit : true ,
887+ },
888+ {
889+ name : "reconcile 2 - offset 0" ,
890+ reconcileCount : 2 ,
891+ vmCount : 10 ,
892+ maxToProcess : 3 ,
893+ expectedOffset : 0 ,
894+ expectedHit : true ,
895+ },
896+ {
897+ name : "reconcile 3 - offset 0" ,
898+ reconcileCount : 3 ,
899+ vmCount : 10 ,
900+ maxToProcess : 3 ,
901+ expectedOffset : 0 ,
902+ expectedHit : true ,
903+ },
904+ // Every 4th reconcile uses reconcileCount as offset (mod vmCount)
905+ {
906+ name : "reconcile 4 - offset 4" ,
907+ reconcileCount : 4 ,
908+ vmCount : 10 ,
909+ maxToProcess : 3 ,
910+ expectedOffset : 4 ,
911+ expectedHit : true ,
912+ },
913+ {
914+ name : "reconcile 5 - offset 0" ,
915+ reconcileCount : 5 ,
916+ vmCount : 10 ,
917+ maxToProcess : 3 ,
918+ expectedOffset : 0 ,
919+ expectedHit : true ,
920+ },
921+ {
922+ name : "reconcile 6 - offset 0" ,
923+ reconcileCount : 6 ,
924+ vmCount : 10 ,
925+ maxToProcess : 3 ,
926+ expectedOffset : 0 ,
927+ expectedHit : true ,
928+ },
929+ {
930+ name : "reconcile 7 - offset 0" ,
931+ reconcileCount : 7 ,
932+ vmCount : 10 ,
933+ maxToProcess : 3 ,
934+ expectedOffset : 0 ,
935+ expectedHit : true ,
936+ },
937+ {
938+ name : "reconcile 8 - offset 8" ,
939+ reconcileCount : 8 ,
940+ vmCount : 10 ,
941+ maxToProcess : 3 ,
942+ expectedOffset : 8 ,
943+ expectedHit : true ,
944+ },
945+ // Test wrap-around when reconcileCount > vmCount
946+ {
947+ name : "reconcile 12 - offset 2 (12 mod 10)" ,
948+ reconcileCount : 12 ,
949+ vmCount : 10 ,
950+ maxToProcess : 3 ,
951+ expectedOffset : 2 , // 12 % 10 = 2
952+ expectedHit : true ,
953+ },
954+ {
955+ name : "reconcile 20 - offset 0 (20 mod 10)" ,
956+ reconcileCount : 20 ,
957+ vmCount : 10 ,
958+ maxToProcess : 3 ,
959+ expectedOffset : 0 , // 20 % 10 = 0
960+ expectedHit : true ,
961+ },
962+ // Edge cases
963+ {
964+ name : "maxToProcess 0 - no limit, returns all" ,
965+ reconcileCount : 4 ,
966+ vmCount : 10 ,
967+ maxToProcess : 0 ,
968+ expectedOffset : 0 , // No limit means all VMs returned starting from 0
969+ expectedHit : false ,
970+ },
971+ {
972+ name : "maxToProcess >= vmCount - no limit hit" ,
973+ reconcileCount : 4 ,
974+ vmCount : 5 ,
975+ maxToProcess : 10 ,
976+ expectedOffset : 0 , // All VMs fit, no rotation needed
977+ expectedHit : false ,
978+ },
979+ }
980+
981+ for _ , tt := range tests {
982+ t .Run (tt .name , func (t * testing.T ) {
983+ controller := & FailoverReservationController {
984+ reconcileCount : tt .reconcileCount ,
985+ }
986+
987+ vms := createVMs (tt .vmCount )
988+ selected , hitLimit := controller .selectVMsToProcess (vms , tt .maxToProcess )
989+
990+ if hitLimit != tt .expectedHit {
991+ t .Errorf ("expected hitLimit=%v, got %v" , tt .expectedHit , hitLimit )
992+ }
993+
994+ if ! tt .expectedHit {
995+ // When no limit is hit, all VMs should be returned
996+ if len (selected ) != tt .vmCount {
997+ t .Errorf ("expected all %d VMs when no limit hit, got %d" , tt .vmCount , len (selected ))
998+ }
999+ return
1000+ }
1001+
1002+ // Verify the first selected VM is at the expected offset
1003+ if len (selected ) == 0 {
1004+ t .Error ("expected at least one VM selected" )
1005+ return
1006+ }
1007+
1008+ // The VMs are sorted by memory descending, so vm-a has most memory, vm-j has least
1009+ // After sorting, the order is: vm-a, vm-b, vm-c, ..., vm-j
1010+ // With offset, we should start at vms[offset]
1011+ expectedFirstVM := vms [tt .expectedOffset ].VM .UUID
1012+ actualFirstVM := selected [0 ].VM .UUID
1013+
1014+ if actualFirstVM != expectedFirstVM {
1015+ t .Errorf ("expected first VM to be %s (offset %d), got %s" ,
1016+ expectedFirstVM , tt .expectedOffset , actualFirstVM )
1017+ }
1018+
1019+ // Verify we got the expected number of VMs
1020+ expectedCount := tt .maxToProcess
1021+ if expectedCount > tt .vmCount {
1022+ expectedCount = tt .vmCount
1023+ }
1024+ if len (selected ) != expectedCount {
1025+ t .Errorf ("expected %d VMs selected, got %d" , expectedCount , len (selected ))
1026+ }
1027+ })
1028+ }
1029+ }
0 commit comments