Skip to content

Commit 6debf2d

Browse files
Fix(gcluster): enforce strict GKE NAP accelerator validation (#5885)
1 parent 47e6345 commit 6debf2d

2 files changed

Lines changed: 97 additions & 10 deletions

File tree

pkg/orchestrator/gke/nap.go

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,28 +30,54 @@ func (g *GKEOrchestrator) isNAPEnabledForMachineType(machineType, zone string) (
3030
resolvedType := config.ResolveMachineType(machineType)
3131

3232
if config.IsTPU(resolvedType) {
33-
key := strings.ToLower(g.GenerateGKENodeSelectorLabel(resolvedType))
34-
return g.napLimits[key] > 0 || g.napLimits["google.com/tpu"] > 0, nil
33+
return g.validateTPUNAPLimit(resolvedType)
3534
}
3635

3736
cap, err := g.FetchMachineCapabilities(resolvedType, zone)
3837
if err != nil {
3938
return false, err
4039
}
4140
if len(cap.Accelerators) > 0 {
42-
key := strings.ToLower(g.GenerateGKENodeSelectorLabel(resolvedType))
43-
if strings.EqualFold(key, resolvedType) {
44-
key = strings.ToLower(g.GenerateGKENodeSelectorLabel(cap.Accelerators[0].Type))
45-
}
46-
if !isKnownGKEAccelerator(key) {
47-
return false, fmt.Errorf("unknown accelerator label: %q", cap.Accelerators[0].Type)
48-
}
49-
return g.napLimits[key] > 0 || g.napLimits["nvidia.com/gpu"] > 0, nil
41+
return g.validateGPUNAPLimit(resolvedType, cap)
5042
}
5143

5244
return g.napLimits["cpu"] > 0, nil
5345
}
5446

47+
func (g *GKEOrchestrator) validateTPUNAPLimit(resolvedType string) (bool, error) {
48+
key := strings.ToLower(g.GenerateGKENodeSelectorLabel(resolvedType))
49+
if limit, exists := g.napLimits[key]; exists {
50+
return limit > 0, nil
51+
}
52+
// Fallback to generic TPU limit ONLY if no specific TPU limits are configured.
53+
for k := range g.napLimits {
54+
if isSpecificTPUKey(k) {
55+
return false, nil
56+
}
57+
}
58+
return g.napLimits["google.com/tpu"] > 0, nil
59+
}
60+
61+
func (g *GKEOrchestrator) validateGPUNAPLimit(resolvedType string, cap MachineTypeCap) (bool, error) {
62+
key := strings.ToLower(g.GenerateGKENodeSelectorLabel(resolvedType))
63+
if strings.EqualFold(key, resolvedType) {
64+
key = strings.ToLower(g.GenerateGKENodeSelectorLabel(cap.Accelerators[0].Type))
65+
}
66+
if !isKnownGKEAccelerator(key) {
67+
return false, fmt.Errorf("unknown accelerator label: %q", cap.Accelerators[0].Type)
68+
}
69+
if limit, exists := g.napLimits[key]; exists {
70+
return limit > 0, nil
71+
}
72+
// Fallback to generic GPU limit ONLY if no specific GPU limits are configured.
73+
for k := range g.napLimits {
74+
if isSpecificGPUKey(k) {
75+
return false, nil
76+
}
77+
}
78+
return g.napLimits["nvidia.com/gpu"] > 0, nil
79+
}
80+
5581
func (g *GKEOrchestrator) checkNAPFlagsSupported(hasNAPFlags bool, job *orchestrator.JobDefinition) error {
5682
if !g.napEnabled && hasNAPFlags {
5783
return fmt.Errorf("GKE NAP provisioning options (--gke-nap-provisioning %q, --gke-nap-reservation %q) are only supported on GKE clusters with Node Auto-Provisioning (NAP) enabled. The current cluster does not have NAP enabled.\nRemediation: Enable Node Auto-Provisioning on your cluster to use these options, or submit your job without them", job.GKENAPProvisioning, job.GKENAPReservation)
@@ -126,6 +152,14 @@ func extractShortReservationName(resName string) string {
126152
return parts[len(parts)-1]
127153
}
128154

155+
func isSpecificGPUKey(key string) bool {
156+
return strings.HasPrefix(key, "nvidia-")
157+
}
158+
159+
func isSpecificTPUKey(key string) bool {
160+
return strings.HasPrefix(key, "tpu-")
161+
}
162+
129163
func isKnownGKEAccelerator(key string) bool {
130164
switch key {
131165
case "nvidia-tesla-t4", "nvidia-tesla-v100":

pkg/orchestrator/gke/resource_resolver_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,46 @@ func TestValidateConsumptionForStaticCluster(t *testing.T) {
784784
wantErr: true,
785785
expectedErr: "unknown accelerator label: \"unknown-gpu\"",
786786
},
787+
{
788+
name: "NAP Cluster - TPU: Specific limit configured, requesting different TPU (Should Fail)",
789+
napEnabled: true,
790+
napLimits: map[string]int64{
791+
"tpu-v6e-slice": 8,
792+
"google.com/tpu": 8,
793+
},
794+
job: orchestrator.JobDefinition{
795+
MachineType: "ct5lp-hightpu-4t", // TPU v5e (tpu-v5-lite-podslice)
796+
GKENAPProvisioning: "spot",
797+
},
798+
wantErr: true,
799+
expectedErr: "is not configured within your cluster's Node Auto-Provisioning (NAP) limits",
800+
},
801+
{
802+
name: "NAP Cluster - GPU: Specific limit configured, requesting different GPU (Should Fail)",
803+
napEnabled: true,
804+
napLimits: map[string]int64{
805+
"nvidia-h100-mega-80gb": 8,
806+
"nvidia.com/gpu": 8,
807+
},
808+
job: orchestrator.JobDefinition{
809+
MachineType: "g2-standard-12", // L4 GPU (nvidia-l4)
810+
GKENAPProvisioning: "spot",
811+
},
812+
wantErr: true,
813+
expectedErr: "is not configured within your cluster's Node Auto-Provisioning (NAP) limits",
814+
},
815+
{
816+
name: "NAP Cluster - GPU: Generic limit only, requesting GPU (Should Pass)",
817+
napEnabled: true,
818+
napLimits: map[string]int64{
819+
"nvidia.com/gpu": 8,
820+
},
821+
job: orchestrator.JobDefinition{
822+
MachineType: "g2-standard-12",
823+
GKENAPProvisioning: "spot",
824+
},
825+
wantErr: false,
826+
},
787827
}
788828

789829
for _, tt := range tests {
@@ -810,6 +850,19 @@ func TestValidateConsumptionForStaticCluster(t *testing.T) {
810850
},
811851
},
812852
},
853+
"g2-standard-12:": {
854+
GuestCpus: 12,
855+
MemoryMb: 48000,
856+
Accelerators: []struct {
857+
Count int `json:"guestAcceleratorCount"`
858+
Type string `json:"guestAcceleratorType"`
859+
}{
860+
{
861+
Count: 1,
862+
Type: "nvidia-l4",
863+
},
864+
},
865+
},
813866
}
814867

815868
err := orc.validateConsumptionForStaticCluster(&tt.job)

0 commit comments

Comments
 (0)