@@ -18,6 +18,8 @@ package customresources
1818
1919import (
2020 "fmt"
21+ "slices"
22+ "strings"
2123 "testing"
2224 "time"
2325
@@ -27,6 +29,7 @@ import (
2729 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2830 "k8s.io/autoscaler/cluster-autoscaler/cloudprovider"
2931 testprovider "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/test"
32+ "k8s.io/autoscaler/cluster-autoscaler/context"
3033 ca_context "k8s.io/autoscaler/cluster-autoscaler/context"
3134 "k8s.io/autoscaler/cluster-autoscaler/utils/gpu"
3235)
@@ -399,3 +402,246 @@ func TestFilterOutNodesWithUnreadyResourcesDRA(t *testing.T) {
399402 }
400403 }
401404}
405+
406+ type testCase struct { // Maps keyed by node name
407+ allNodes map [string ]* apiv1.Node
408+ readyNodes []* apiv1.Node
409+ wantNodesWithUnreadyOverride []string
410+ }
411+
412+ func TestFilterOutNodesWithUnreadyResourcesRefactor (t * testing.T ) {
413+ start := time .Now ()
414+ later := start .Add (10 * time .Minute )
415+ // Any non-zero resource quantity value on a Ready node indicates GPU is ready.
416+ resourceQuantityOne := * resource .NewQuantity (1 , resource .DecimalSI )
417+ // A zero resource quantity value on a Ready node indicates GPU is unready.
418+ resourceQuantityZero := * resource .NewQuantity (0 , resource .DecimalSI )
419+ readyCondition := apiv1.NodeCondition {
420+ Type : apiv1 .NodeReady ,
421+ Status : apiv1 .ConditionTrue ,
422+ LastTransitionTime : metav1 .NewTime (later ),
423+ }
424+ unreadyCondition := apiv1.NodeCondition {
425+ Type : apiv1 .NodeReady ,
426+ Status : apiv1 .ConditionFalse ,
427+ LastTransitionTime : metav1 .NewTime (later ),
428+ }
429+ cases := []testCase {
430+ {
431+ allNodes : map [string ]* apiv1.Node {
432+ "nodeGpuReady" : {
433+ ObjectMeta : metav1.ObjectMeta {
434+ Name : "nodeGpuReady" ,
435+ Labels : gpuLabels ,
436+ CreationTimestamp : metav1 .NewTime (start ),
437+ },
438+ Status : apiv1.NodeStatus {
439+ Capacity : apiv1.ResourceList {
440+ gpu .ResourceNvidiaGPU : resourceQuantityOne ,
441+ },
442+ Allocatable : apiv1.ResourceList {
443+ gpu .ResourceNvidiaGPU : resourceQuantityOne ,
444+ },
445+ Conditions : []apiv1.NodeCondition {readyCondition },
446+ },
447+ },
448+ "nodeGpuReadyDRA" : {
449+ ObjectMeta : metav1.ObjectMeta {
450+ Name : "nodeGpuReadyDRA" ,
451+ CreationTimestamp : metav1 .NewTime (start ),
452+ },
453+ Status : apiv1.NodeStatus {
454+ Conditions : []apiv1.NodeCondition {readyCondition },
455+ },
456+ },
457+ "nodeDirectXReady" : {
458+ ObjectMeta : metav1.ObjectMeta {
459+ Name : "nodeDirectXReady" ,
460+ Labels : gpuLabels ,
461+ CreationTimestamp : metav1 .NewTime (start ),
462+ },
463+ Status : apiv1.NodeStatus {
464+ Capacity : apiv1.ResourceList {
465+ gpu .ResourceDirectX : resourceQuantityOne ,
466+ },
467+ Allocatable : apiv1.ResourceList {
468+ gpu .ResourceDirectX : resourceQuantityOne ,
469+ },
470+ Conditions : []apiv1.NodeCondition {readyCondition },
471+ },
472+ },
473+ "nodeVanillaReady" : {
474+ ObjectMeta : metav1.ObjectMeta {
475+ Name : "nodeVanillaReady" ,
476+ Labels : make (map [string ]string ),
477+ CreationTimestamp : metav1 .NewTime (start ),
478+ },
479+ Status : apiv1.NodeStatus {
480+ Conditions : []apiv1.NodeCondition {readyCondition },
481+ },
482+ },
483+ "nodeGpuUnready" : {
484+ ObjectMeta : metav1.ObjectMeta {
485+ Name : "nodeGpuUnready" ,
486+ Labels : gpuLabels ,
487+ CreationTimestamp : metav1 .NewTime (start ),
488+ },
489+ Status : apiv1.NodeStatus {
490+ Capacity : apiv1.ResourceList {
491+ gpu .ResourceNvidiaGPU : resourceQuantityZero ,
492+ },
493+ Allocatable : apiv1.ResourceList {
494+ gpu .ResourceNvidiaGPU : resourceQuantityZero ,
495+ },
496+ Conditions : []apiv1.NodeCondition {readyCondition },
497+ },
498+ },
499+ "nodeDirectXUnready" : {
500+ ObjectMeta : metav1.ObjectMeta {
501+ Name : "nodeDirectXUnready" ,
502+ Labels : gpuLabels ,
503+ CreationTimestamp : metav1 .NewTime (start ),
504+ },
505+ Status : apiv1.NodeStatus {
506+ Capacity : apiv1.ResourceList {
507+ gpu .ResourceDirectX : resourceQuantityZero ,
508+ },
509+ Allocatable : apiv1.ResourceList {
510+ gpu .ResourceDirectX : resourceQuantityZero ,
511+ },
512+ Conditions : []apiv1.NodeCondition {readyCondition },
513+ },
514+ },
515+ "nodeGpuUnready2" : {
516+ ObjectMeta : metav1.ObjectMeta {
517+ Name : "nodeGpuUnready2" ,
518+ Labels : gpuLabels ,
519+ CreationTimestamp : metav1 .NewTime (start ),
520+ },
521+ Status : apiv1.NodeStatus {
522+ Conditions : []apiv1.NodeCondition {readyCondition },
523+ },
524+ },
525+ "nodeVanillaNotReady" : {
526+ ObjectMeta : metav1.ObjectMeta {
527+ Name : "nodeVanillaNotReady" ,
528+ Labels : make (map [string ]string ),
529+ CreationTimestamp : metav1 .NewTime (start ),
530+ },
531+ Status : apiv1.NodeStatus {
532+ Conditions : []apiv1.NodeCondition {unreadyCondition },
533+ },
534+ },
535+ },
536+ readyNodes : []* apiv1.Node {
537+ {
538+ ObjectMeta : metav1.ObjectMeta {
539+ Name : "nodeGpuReady" ,
540+ Labels : gpuLabels ,
541+ CreationTimestamp : metav1 .NewTime (start ),
542+ },
543+ Status : apiv1.NodeStatus {
544+ Capacity : apiv1.ResourceList {
545+ gpu .ResourceNvidiaGPU : resourceQuantityOne ,
546+ },
547+ Allocatable : apiv1.ResourceList {
548+ gpu .ResourceNvidiaGPU : resourceQuantityOne ,
549+ },
550+ Conditions : []apiv1.NodeCondition {readyCondition },
551+ },
552+ },
553+ {
554+ ObjectMeta : metav1.ObjectMeta {
555+ Name : "nodeGpuReadyDRA" ,
556+ CreationTimestamp : metav1 .NewTime (start ),
557+ },
558+ Status : apiv1.NodeStatus {
559+ Conditions : []apiv1.NodeCondition {readyCondition },
560+ },
561+ },
562+ {
563+ ObjectMeta : metav1.ObjectMeta {
564+ Name : "nodeDirectXReady" ,
565+ Labels : gpuLabels ,
566+ CreationTimestamp : metav1 .NewTime (start ),
567+ },
568+ Status : apiv1.NodeStatus {
569+ Capacity : apiv1.ResourceList {
570+ gpu .ResourceDirectX : resourceQuantityOne ,
571+ },
572+ Allocatable : apiv1.ResourceList {
573+ gpu .ResourceDirectX : resourceQuantityOne ,
574+ },
575+ Conditions : []apiv1.NodeCondition {readyCondition },
576+ },
577+ },
578+ {
579+ ObjectMeta : metav1.ObjectMeta {
580+ Name : "nodeVanillaReady" ,
581+ Labels : make (map [string ]string ),
582+ CreationTimestamp : metav1 .NewTime (start ),
583+ },
584+ Status : apiv1.NodeStatus {
585+ Conditions : []apiv1.NodeCondition {readyCondition },
586+ },
587+ },
588+ },
589+ wantNodesWithUnreadyOverride : []string {
590+ "nodeGpuUnready" ,
591+ "nodeDirectXUnready" ,
592+ "nodeGpuUnready2" ,
593+ },
594+ },
595+ }
596+
597+ nodeGpuConfig := func (node * apiv1.Node ) * cloudprovider.GpuConfig {
598+ var draDriverName string
599+ if strings .Contains (node .Name , "DRA" ) {
600+ draDriverName = "gpu.nvidia.com"
601+ }
602+ return & cloudprovider.GpuConfig {
603+ ExtendedResourceName : "" ,
604+ DraDriverName : draDriverName ,
605+ }
606+ }
607+ provider := testprovider .NewTestCloudProviderBuilder ().WithNodeGpuConfig (nodeGpuConfig ).Build ()
608+
609+ for _ , tc := range cases {
610+ processor := GpuCustomResourcesProcessor {}
611+ autoscalingCtx := & ca_context.AutoscalingContext {CloudProvider : provider }
612+ gotAllNodes , gotReadyNodes := processor .FilterOutNodesWithUnreadyResources (autoscalingCtx , toList (tc .allNodes ), tc .readyNodes , nil )
613+ gotAllNodesSet := toSet (gotAllNodes )
614+ for _ , v := range tc .allNodes {
615+ if _ , found := gotAllNodesSet [v .Name ]; ! found {
616+ t .Errorf ("FilterOutNodesWithUnreadyResources() missing node %v from all nodes list" , v .Name )
617+ }
618+ if v .Status .Conditions [0 ].Status == apiv1 .ConditionTrue && gotAllNodesSet [v .Name ].Status .Conditions [0 ].Status == apiv1 .ConditionFalse {
619+ assert .Contains (t , tc .wantNodesWithUnreadyOverride , v .Name , fmt .Sprintf ("FilterOutNodesWithUnreadyResources() node %v status condition is unready, but was not expected to be" , v .Name ))
620+ assert .Len (t , gotAllNodesSet [v .Name ].Status .Conditions , 1 , fmt .Sprintf ("FilterOutNodesWithUnreadyResources() node %v status conditions count does not match expected" , v .Name ))
621+ assert .Equal (t , v .ObjectMeta , gotAllNodesSet [v .Name ].ObjectMeta , fmt .Sprintf ("FilterOutNodesWithUnreadyResources() node %v metadata does not match expected" , v .Name ))
622+ }
623+ }
624+ assert .Equal (t , gotReadyNodes , tc .readyNodes , fmt .Sprintf ("FilterOutNodesWithUnreadyResources() node %v in ready nodes list does not match expected" , gotReadyNodes ))
625+ for _ , v := range gotAllNodesSet {
626+ if ! slices .Contains (tc .wantNodesWithUnreadyOverride , v .Name ) {
627+ assert .Equal (t , v , tc .allNodes [v .Name ], fmt .Sprintf ("FilterOutNodesWithUnreadyResources() node %v in all nodes list does not match expected" , v .Name ))
628+ }
629+ }
630+ }
631+ }
632+
633+ func toList (m map [string ]* apiv1.Node ) []* apiv1.Node {
634+ var l []* apiv1.Node
635+ for _ , n := range m {
636+ l = append (l , n )
637+ }
638+ return l
639+ }
640+
641+ func toSet (l []* apiv1.Node ) map [string ]* apiv1.Node {
642+ m := make (map [string ]* apiv1.Node )
643+ for _ , n := range l {
644+ m [n .Name ] = n
645+ }
646+ return m
647+ }
0 commit comments