@@ -29,17 +29,16 @@ import (
2929 "sigs.k8s.io/cluster-api/util"
3030)
3131
32- // InsufficientMemoryError is used when the scheduler cannot assign a VM to a node because it would
33- // exceed the node's memory limit.
34- type InsufficientMemoryError struct {
35- node string
36- available uint64
37- requested uint64
32+ // InsufficientResourcesError is used when the scheduler cannot assign a VM to a node because no node
33+ // would be able to provide the requested resources.
34+ type InsufficientResourcesError struct {
35+ requestedMemory uint64
36+ requestedCores uint64
3837}
3938
40- func (err InsufficientMemoryError ) Error () string {
41- return fmt .Sprintf ("cannot reserve %dB of memory on node %s: %dB available memory left " ,
42- err .requested , err .node , err . available )
39+ func (err InsufficientResourcesError ) Error () string {
40+ return fmt .Sprintf ("cannot reserve %dB of memory and/or %d vCores in cluster " ,
41+ err .requestedMemory , err .requestedCores )
4342}
4443
4544// ScheduleVM decides which node to a ProxmoxMachine should be scheduled on.
@@ -64,69 +63,92 @@ func selectNode(
6463 allowedNodes []string ,
6564 schedulerHints * infrav1.SchedulerHints ,
6665) (string , error ) {
67- byMemory := make (sortByAvailableMemory , len (allowedNodes ))
68- for i , nodeName := range allowedNodes {
69- mem , err := client .GetReservableMemoryBytes (ctx , nodeName , schedulerHints .GetMemoryAdjustment ())
66+ var nodes []nodeInfo
67+
68+ requestedMemory := uint64 (machine .Spec .MemoryMiB ) * 1024 * 1024 // convert to bytes
69+ requestedCores := uint64 (machine .Spec .NumCores )
70+
71+ for _ , nodeName := range allowedNodes {
72+ mem , cpu , err := client .GetReservableResources (
73+ ctx ,
74+ nodeName ,
75+ schedulerHints .GetMemoryAdjustment (),
76+ schedulerHints .GetCPUAdjustment (),
77+ )
7078 if err != nil {
7179 return "" , err
7280 }
73- byMemory [i ] = nodeInfo {Name : nodeName , AvailableMemory : mem }
74- }
7581
76- sort .Sort (byMemory )
82+ // if MemoryAdjustment is explicitly set to 0 (zero), pretend we have enough mem for the guest
83+ if schedulerHints .GetMemoryAdjustment () == 0 {
84+ mem = requestedMemory
85+ }
86+ // if CPUAdjustment is explicitly set to 0 (zero), pretend we have enough cpu for the guest
87+ if schedulerHints .GetCPUAdjustment () == 0 {
88+ cpu = requestedCores
89+ }
7790
78- requestedMemory := uint64 (machine .Spec .MemoryMiB ) * 1024 * 1024 // convert to bytes
79- if requestedMemory > byMemory [0 ].AvailableMemory {
80- // no more space on the node with the highest amount of available memory
81- return "" , InsufficientMemoryError {
82- node : byMemory [0 ].Name ,
83- available : byMemory [0 ].AvailableMemory ,
84- requested : requestedMemory ,
91+ node := nodeInfo {Name : nodeName , AvailableMemory : mem , AvailableCPU : cpu }
92+ if node .AvailableMemory >= requestedMemory && node .AvailableCPU >= requestedCores {
93+ nodes = append (nodes , node )
8594 }
8695 }
8796
97+ if len (nodes ) == 0 {
98+ return "" , InsufficientResourcesError {requestedMemory , requestedCores }
99+ }
100+
101+ // Sort nodes by free memory and then free CPU in descending order
102+ byResources := make (sortByResources , len (nodes ))
103+ copy (byResources , nodes )
104+ sort .Sort (byResources )
105+
106+ decision := byResources [0 ].Name
107+
88108 // count the existing vms per node
89109 nodeCounter := make (map [string ]int )
90110 for _ , nl := range locations {
91111 nodeCounter [nl .Node ]++
92112 }
93113
94- for i , info := range byMemory {
114+ for i , info := range byResources {
95115 info .ScheduledVMs = nodeCounter [info .Name ]
96- byMemory [i ] = info
116+ byResources [i ] = info
97117 }
98118
99- byReplicas := make (sortByReplicas , len (byMemory ))
100- copy (byReplicas , byMemory )
119+ byReplicas := make (sortByReplicas , len (byResources ))
120+ copy (byReplicas , byResources )
101121
102122 sort .Sort (byReplicas )
103123
104- decision := byMemory [0 ].Name
105- if requestedMemory < byReplicas [0 ].AvailableMemory {
106- // distribute round-robin when memory allows it
124+ // if memory allocation allows it, pick the node with the least amount of guests
125+ if schedulerHints .PreferLowerGuestCount {
107126 decision = byReplicas [0 ].Name
108127 }
109128
110129 if logger := logr .FromContextOrDiscard (ctx ); logger .V (4 ).Enabled () {
111130 // only construct values when message should actually be logged
112131 logger .Info ("Scheduler decision" ,
113132 "byReplicas" , byReplicas .String (),
114- "byMemory " , byMemory .String (),
133+ "byResources " , byResources .String (),
115134 "requestedMemory" , requestedMemory ,
135+ "requestedCores" , requestedCores ,
116136 "resultNode" , decision ,
137+ "schedulerHints" , schedulerHints ,
117138 )
118139 }
119140
120141 return decision , nil
121142}
122143
123144type resourceClient interface {
124- GetReservableMemoryBytes (context.Context , string , uint64 ) (uint64 , error )
145+ GetReservableResources (context.Context , string , uint64 , uint64 ) (uint64 , uint64 , error )
125146}
126147
127148type nodeInfo struct {
128149 Name string `json:"node"`
129150 AvailableMemory uint64 `json:"mem"`
151+ AvailableCPU uint64 `json:"cpu"`
130152 ScheduledVMs int `json:"vms"`
131153}
132154
@@ -143,16 +165,21 @@ func (a sortByReplicas) String() string {
143165 return string (o )
144166}
145167
146- type sortByAvailableMemory []nodeInfo
168+ type sortByResources []nodeInfo
169+
170+ func (a sortByResources ) Len () int { return len (a ) }
171+ func (a sortByResources ) Swap (i , j int ) { a [i ], a [j ] = a [j ], a [i ] }
172+ func (a sortByResources ) Less (i , j int ) bool {
173+ // Compare by free memory and free CPU in descending order
174+ if a [i ].AvailableMemory != a [j ].AvailableMemory {
175+ return a [i ].AvailableMemory > a [j ].AvailableMemory
176+ }
147177
148- func (a sortByAvailableMemory ) Len () int { return len (a ) }
149- func (a sortByAvailableMemory ) Swap (i , j int ) { a [i ], a [j ] = a [j ], a [i ] }
150- func (a sortByAvailableMemory ) Less (i , j int ) bool {
151- // more available memory = lower index
152- return a [i ].AvailableMemory > a [j ].AvailableMemory
178+ // If free memory is equal, sort by free CPU in descending order
179+ return a [i ].AvailableCPU > a [j ].AvailableCPU || (a [i ].AvailableCPU == a [j ].AvailableCPU && a [i ].ScheduledVMs < a [j ].ScheduledVMs )
153180}
154181
155- func (a sortByAvailableMemory ) String () string {
182+ func (a sortByResources ) String () string {
156183 o , _ := json .Marshal (a )
157184 return string (o )
158185}
0 commit comments