@@ -223,6 +223,244 @@ func TestManagerRequiresResourceSlices(t *testing.T) {
223223 })
224224}
225225
226+ func TestNodeResourcesAffectDeviceCounts (t * testing.T ) {
227+ nicConfig := Config {
228+ Enabled : true ,
229+ Classes : []ClassConfig {
230+ {
231+ Name : "nic" ,
232+ Enabled : true ,
233+ Labels : Labels {
234+ Current : testNICCountCurrentLabel ,
235+ Expected : testNICCountExpectedLabel ,
236+ },
237+ CurrentExpression : "int(node.status.allocatable['nvidia.com/mlnxnics'])" ,
238+ },
239+ },
240+ }
241+
242+ gpuOnlyConfig := Config {
243+ Enabled : true ,
244+ Classes : []ClassConfig {
245+ {
246+ Name : "gpu" ,
247+ Enabled : true ,
248+ Labels : Labels {
249+ Current : testGPUCountCurrentLabel ,
250+ Expected : testGPUCountExpectedLabel ,
251+ },
252+ CurrentExpression : "int(node.metadata.labels['nvidia.com/gpu.count'])" ,
253+ },
254+ },
255+ }
256+
257+ t .Run ("triggers on allocatable NIC count change" , func (t * testing.T ) {
258+ manager := newTestManager (t , nicConfig )
259+ oldNode := testNode ("node-a" , map [string ]string {})
260+ oldNode .Status .Allocatable = corev1.ResourceList {
261+ corev1 .ResourceName ("nvidia.com/mlnxnics" ): resource .MustParse ("0" ),
262+ }
263+
264+ newNode := oldNode .DeepCopy ()
265+ newNode .Status .Allocatable = corev1.ResourceList {
266+ corev1 .ResourceName ("nvidia.com/mlnxnics" ): resource .MustParse ("4" ),
267+ }
268+
269+ require .True (t , manager .NodeResourcesAffectDeviceCounts (oldNode , newNode ))
270+ })
271+
272+ t .Run ("triggers on allocatable key added" , func (t * testing.T ) {
273+ manager := newTestManager (t , nicConfig )
274+ oldNode := testNode ("node-a" , map [string ]string {})
275+
276+ newNode := oldNode .DeepCopy ()
277+ newNode .Status .Allocatable = corev1.ResourceList {
278+ corev1 .ResourceName ("nvidia.com/mlnxnics" ): resource .MustParse ("4" ),
279+ }
280+
281+ require .True (t , manager .NodeResourcesAffectDeviceCounts (oldNode , newNode ))
282+ })
283+
284+ t .Run ("triggers on allocatable key removed" , func (t * testing.T ) {
285+ manager := newTestManager (t , nicConfig )
286+ oldNode := testNode ("node-a" , map [string ]string {})
287+ oldNode .Status .Allocatable = corev1.ResourceList {
288+ corev1 .ResourceName ("nvidia.com/mlnxnics" ): resource .MustParse ("4" ),
289+ }
290+
291+ newNode := oldNode .DeepCopy ()
292+ newNode .Status .Allocatable = nil
293+
294+ require .True (t , manager .NodeResourcesAffectDeviceCounts (oldNode , newNode ))
295+ })
296+
297+ t .Run ("triggers on capacity change" , func (t * testing.T ) {
298+ capacityConfig := Config {
299+ Enabled : true ,
300+ Classes : []ClassConfig {
301+ {
302+ Name : "nic" ,
303+ Enabled : true ,
304+ Labels : Labels {
305+ Current : testNICCountCurrentLabel ,
306+ Expected : testNICCountExpectedLabel ,
307+ },
308+ CurrentExpression : "int(node.status.capacity['nvidia.com/mlnxnics'])" ,
309+ },
310+ },
311+ }
312+
313+ manager := newTestManager (t , capacityConfig )
314+ oldNode := testNode ("node-a" , map [string ]string {})
315+ oldNode .Status .Capacity = corev1.ResourceList {
316+ corev1 .ResourceName ("nvidia.com/mlnxnics" ): resource .MustParse ("4" ),
317+ }
318+
319+ newNode := oldNode .DeepCopy ()
320+ newNode .Status .Capacity = corev1.ResourceList {
321+ corev1 .ResourceName ("nvidia.com/mlnxnics" ): resource .MustParse ("8" ),
322+ }
323+
324+ require .True (t , manager .NodeResourcesAffectDeviceCounts (oldNode , newNode ))
325+ })
326+
327+ t .Run ("does not trigger when allocatable is unchanged" , func (t * testing.T ) {
328+ manager := newTestManager (t , nicConfig )
329+ oldNode := testNode ("node-a" , map [string ]string {})
330+ oldNode .Status .Allocatable = corev1.ResourceList {
331+ corev1 .ResourceName ("nvidia.com/mlnxnics" ): resource .MustParse ("4" ),
332+ }
333+
334+ newNode := oldNode .DeepCopy ()
335+
336+ require .False (t , manager .NodeResourcesAffectDeviceCounts (oldNode , newNode ))
337+ })
338+
339+ t .Run ("does not trigger for nil vs empty allocatable" , func (t * testing.T ) {
340+ manager := newTestManager (t , nicConfig )
341+ oldNode := testNode ("node-a" , map [string ]string {})
342+ oldNode .Status .Allocatable = nil
343+
344+ newNode := oldNode .DeepCopy ()
345+ newNode .Status .Allocatable = corev1.ResourceList {}
346+
347+ require .False (t , manager .NodeResourcesAffectDeviceCounts (oldNode , newNode ))
348+ })
349+
350+ t .Run ("does not trigger when no class references node status" , func (t * testing.T ) {
351+ manager := newTestManager (t , gpuOnlyConfig )
352+ oldNode := testNode ("node-a" , map [string ]string {
353+ "nvidia.com/gpu.count" : "8" ,
354+ })
355+ oldNode .Status .Allocatable = corev1.ResourceList {
356+ corev1 .ResourceName ("nvidia.com/mlnxnics" ): resource .MustParse ("0" ),
357+ }
358+
359+ newNode := oldNode .DeepCopy ()
360+ newNode .Status .Allocatable = corev1.ResourceList {
361+ corev1 .ResourceName ("nvidia.com/mlnxnics" ): resource .MustParse ("4" ),
362+ }
363+
364+ require .False (t , manager .NodeResourcesAffectDeviceCounts (oldNode , newNode ))
365+ })
366+
367+ t .Run ("detects change when one of multiple classes references status" , func (t * testing.T ) {
368+ mixedConfig := Config {
369+ Enabled : true ,
370+ Classes : []ClassConfig {
371+ {
372+ Name : "gpu" ,
373+ Enabled : true ,
374+ Labels : Labels {
375+ Current : testGPUCountCurrentLabel ,
376+ Expected : testGPUCountExpectedLabel ,
377+ },
378+ CurrentExpression : "int(node.metadata.labels['nvidia.com/gpu.count'])" ,
379+ },
380+ {
381+ Name : "nic" ,
382+ Enabled : true ,
383+ Labels : Labels {
384+ Current : testNICCountCurrentLabel ,
385+ Expected : testNICCountExpectedLabel ,
386+ },
387+ CurrentExpression : "int(node.status.allocatable['nvidia.com/mlnxnics'])" ,
388+ },
389+ },
390+ }
391+
392+ manager := newTestManager (t , mixedConfig )
393+ oldNode := testNode ("node-a" , map [string ]string {
394+ "nvidia.com/gpu.count" : "8" ,
395+ })
396+ oldNode .Status .Allocatable = corev1.ResourceList {
397+ corev1 .ResourceName ("nvidia.com/mlnxnics" ): resource .MustParse ("0" ),
398+ }
399+
400+ newNode := oldNode .DeepCopy ()
401+ newNode .Status .Allocatable = corev1.ResourceList {
402+ corev1 .ResourceName ("nvidia.com/mlnxnics" ): resource .MustParse ("4" ),
403+ }
404+
405+ require .True (t , manager .NodeResourcesAffectDeviceCounts (oldNode , newNode ))
406+ })
407+
408+ t .Run ("returns false for disabled manager" , func (t * testing.T ) {
409+ var manager * Manager
410+ oldNode := testNode ("node-a" , map [string ]string {})
411+ newNode := oldNode .DeepCopy ()
412+
413+ require .False (t , manager .NodeResourcesAffectDeviceCounts (oldNode , newNode ))
414+ })
415+ }
416+
417+ func TestReferencesNodeResources (t * testing.T ) {
418+ t .Run ("returns true for allocatable expression" , func (t * testing.T ) {
419+ class := compiledClass {
420+ ClassConfig : ClassConfig {
421+ CurrentExpression : "int(node.status.allocatable['nvidia.com/mlnxnics'])" ,
422+ },
423+ }
424+ require .True (t , class .referencesNodeResources ())
425+ })
426+
427+ t .Run ("returns true for capacity expression" , func (t * testing.T ) {
428+ class := compiledClass {
429+ ClassConfig : ClassConfig {
430+ CurrentExpression : "int(node.status.capacity['nvidia.com/mlnxnics'])" ,
431+ },
432+ }
433+ require .True (t , class .referencesNodeResources ())
434+ })
435+
436+ t .Run ("returns false for label expression" , func (t * testing.T ) {
437+ class := compiledClass {
438+ ClassConfig : ClassConfig {
439+ CurrentExpression : "int(node.metadata.labels['nvidia.com/gpu.count'])" ,
440+ },
441+ }
442+ require .False (t , class .referencesNodeResources ())
443+ })
444+
445+ t .Run ("returns false for resourceSlices expression" , func (t * testing.T ) {
446+ class := compiledClass {
447+ ClassConfig : ClassConfig {
448+ CurrentExpression : "resourceSlices.size()" ,
449+ },
450+ }
451+ require .False (t , class .referencesNodeResources ())
452+ })
453+
454+ t .Run ("returns false for conditions expression" , func (t * testing.T ) {
455+ class := compiledClass {
456+ ClassConfig : ClassConfig {
457+ CurrentExpression : "node.status.conditions.exists(c, c.type == 'Ready')" ,
458+ },
459+ }
460+ require .False (t , class .referencesNodeResources ())
461+ })
462+ }
463+
226464func newTestManager (t * testing.T , config Config ) * Manager {
227465 t .Helper ()
228466
0 commit comments