@@ -77,6 +77,31 @@ func TestImportNamespace(t *testing.T) {
7777 ResourceGroup (
7878 * utiltestingapi .MakeFlavorQuotas ("f1" ).Resource (corev1 .ResourceCPU , "1" , "0" ).Obj ())
7979
80+ baseGpuPodWrapper := testingpod .MakePod ("pod-gpu" , testingNamespace ).
81+ UID ("pod-gpu" ).
82+ Label (testingQueueLabel , "q1" ).
83+ Image ("img" , nil ).
84+ Request (corev1 .ResourceCPU , "1" ).
85+ Request (corev1 .ResourceName ("nvidia.com/gpu" ), "1" )
86+ baseGpuManagedPodWrapper := baseGpuPodWrapper .Clone ().
87+ Label (controllerconstants .QueueLabel , "lq1" ).
88+ ManagedByKueueLabel ()
89+
90+ cpuOnlyClusterQueue :=
91+ * utiltestingapi .MakeClusterQueue ("cq1" ).
92+ ResourceGroup (
93+ * utiltestingapi .MakeFlavorQuotas ("cpu-flavor" ).
94+ Resource (corev1 .ResourceCPU , "10" , "0" ).
95+ Obj (),
96+ )
97+
98+ cpuAndGpuClusterQueue :=
99+ cpuOnlyClusterQueue .Clone ().ResourceGroup (
100+ * utiltestingapi .MakeFlavorQuotas ("gpu-flavor" ).
101+ Resource (corev1 .ResourceName ("nvidia.com/gpu" ), "10" , "0" ).
102+ Obj (),
103+ )
104+
80105 podCmpOpts := cmp.Options {
81106 cmpopts .EquateEmpty (),
82107 cmpopts .IgnoreFields (metav1.ObjectMeta {}, "ResourceVersion" ),
@@ -88,47 +113,33 @@ func TestImportNamespace(t *testing.T) {
88113 cmpopts .IgnoreFields (metav1.Condition {}, "ObservedGeneration" , "LastTransitionTime" ),
89114 }
90115
116+ defaultMappingRules := mapping.Rules {{
117+ Match : mapping.Match {Labels : map [string ]string {testingQueueLabel : "q1" }},
118+ ToLocalQueue : "lq1" ,
119+ }}
120+
91121 cases := map [string ]struct {
92- pods []corev1.Pod
93- clusterQueues []kueue.ClusterQueue
94- localQueues []kueue.LocalQueue
95- mapping mapping.Rules
96- addLabels map [string ]string
97-
98- wantPods []corev1.Pod
99- wantWorkloads []kueue.Workload
100- wantError error
122+ pods []corev1.Pod
123+ clusterQueue kueue.ClusterQueue
124+ localQueue kueue.LocalQueue
125+ addLabels map [string ]string
126+ wantPods []corev1.Pod
127+ wantWorkloads []kueue.Workload
128+ wantError error
129+ wantAdmissionOnlyCompare bool
101130 }{
102-
103131 "create one" : {
104132 pods : []corev1.Pod {
105133 * basePodWrapper .DeepCopy (),
106134 },
107- mapping : mapping.Rules {
108- mapping.Rule {
109- Match : mapping.Match {
110- PriorityClassName : "" ,
111- Labels : map [string ]string {
112- testingQueueLabel : "q1" ,
113- },
114- },
115- ToLocalQueue : "lq1" ,
116- },
117- },
118- localQueues : []kueue.LocalQueue {
119- * baseLocalQueue .Obj (),
120- },
121- clusterQueues : []kueue.ClusterQueue {
122- * baseClusterQueue .Obj (),
123- },
124-
135+ localQueue : * baseLocalQueue .Obj (),
136+ clusterQueue : * baseClusterQueue .Obj (),
125137 wantPods : []corev1.Pod {
126138 * basePodWrapper .Clone ().
127139 Label (controllerconstants .QueueLabel , "lq1" ).
128140 ManagedByKueueLabel ().
129141 Obj (),
130142 },
131-
132143 wantWorkloads : []kueue.Workload {
133144 * baseWlWrapper .DeepCopy (),
134145 },
@@ -137,48 +148,67 @@ func TestImportNamespace(t *testing.T) {
137148 pods : []corev1.Pod {
138149 * basePodWrapper .DeepCopy (),
139150 },
140- mapping : mapping.Rules {
141- mapping.Rule {
142- Match : mapping.Match {
143- PriorityClassName : "" ,
144- Labels : map [string ]string {
145- testingQueueLabel : "q1" ,
146- },
147- },
148- ToLocalQueue : "lq1" ,
149- },
150- },
151- localQueues : []kueue.LocalQueue {
152- * baseLocalQueue .Obj (),
153- },
154- clusterQueues : []kueue.ClusterQueue {
155- * baseClusterQueue .Obj (),
156- },
151+ localQueue : * baseLocalQueue .Obj (),
152+ clusterQueue : * baseClusterQueue .Obj (),
157153 addLabels : map [string ]string {
158154 "new.lbl" : "val" ,
159155 },
160-
161156 wantPods : []corev1.Pod {
162157 * basePodWrapper .Clone ().
163158 Label (controllerconstants .QueueLabel , "lq1" ).
164159 ManagedByKueueLabel ().
165160 Label ("new.lbl" , "val" ).
166161 Obj (),
167162 },
168-
169163 wantWorkloads : []kueue.Workload {
170164 * baseWlWrapper .Clone ().
171165 Label ("new.lbl" , "val" ).
172166 Obj (),
173167 },
174168 },
169+ "imports a pod requesting cpu and gpu and assigns each resource to its matching resource-group flavor" : {
170+ pods : []corev1.Pod {
171+ * baseGpuPodWrapper .DeepCopy (),
172+ },
173+ localQueue : * baseLocalQueue .Obj (),
174+ clusterQueue : * cpuAndGpuClusterQueue .Obj (),
175+ wantPods : []corev1.Pod {
176+ * baseGpuManagedPodWrapper .DeepCopy (),
177+ },
178+ wantAdmissionOnlyCompare : true ,
179+ wantWorkloads : []kueue.Workload {{
180+ Status : kueue.WorkloadStatus {Admission : & kueue.Admission {
181+ ClusterQueue : "cq1" ,
182+ PodSetAssignments : []kueue.PodSetAssignment {{
183+ Name : kueue .DefaultPodSetName ,
184+ Flavors : map [corev1.ResourceName ]kueue.ResourceFlavorReference {
185+ corev1 .ResourceCPU : "cpu-flavor" ,
186+ corev1 .ResourceName ("nvidia.com/gpu" ): "gpu-flavor" ,
187+ },
188+ }},
189+ }},
190+ }},
191+ },
192+ "returns an error and leaves admission unset when a requested resource is not covered by the cluster queue" : {
193+ pods : []corev1.Pod {
194+ * baseGpuPodWrapper .DeepCopy (),
195+ },
196+ localQueue : * baseLocalQueue .Obj (),
197+ clusterQueue : * cpuOnlyClusterQueue .Obj (),
198+ wantError : & resourceNotCoveredError {Resource : corev1 .ResourceName ("nvidia.com/gpu" ), ClusterQueue : "cq1" },
199+ wantPods : []corev1.Pod {
200+ * baseGpuManagedPodWrapper .DeepCopy (),
201+ },
202+ wantAdmissionOnlyCompare : true ,
203+ wantWorkloads : []kueue.Workload {{}},
204+ },
175205 }
176206
177207 for name , tc := range cases {
178208 t .Run (name , func (t * testing.T ) {
179209 podsList := corev1.PodList {Items : tc .pods }
180- cqList := kueue.ClusterQueueList {Items : tc .clusterQueues }
181- lqList := kueue.LocalQueueList {Items : tc .localQueues }
210+ cqList := kueue.ClusterQueueList {Items : []kueue. ClusterQueue { tc .clusterQueue } }
211+ lqList := kueue.LocalQueueList {Items : []kueue. LocalQueue { tc .localQueue } }
182212
183213 builder := utiltesting .NewClientBuilder ().
184214 WithInterceptorFuncs (interceptor.Funcs {SubResourcePatch : utiltesting .TreatSSAAsStrategicMerge }).WithStatusSubresource (& kueue.Workload {}).
@@ -187,7 +217,7 @@ func TestImportNamespace(t *testing.T) {
187217 client := builder .Build ()
188218 ctx , _ := utiltesting .ContextWithLog (t )
189219
190- mpc , _ := cache .Load (ctx , client , []string {testingNamespace }, tc . mapping , tc .addLabels )
220+ mpc , _ := cache .Load (ctx , client , []string {testingNamespace }, defaultMappingRules , tc .addLabels )
191221 gotErr := Import (ctx , client , mpc , 8 )
192222
193223 if diff := cmp .Diff (tc .wantError , gotErr , cmpopts .EquateErrors ()); diff != "" {
@@ -207,9 +237,80 @@ func TestImportNamespace(t *testing.T) {
207237 if err != nil {
208238 t .Errorf ("Unexpected list workloads error: %s" , err )
209239 }
210- if diff := cmp .Diff (tc .wantWorkloads , wlList .Items , wlCmpOpts ... ); diff != "" {
240+ if tc .wantAdmissionOnlyCompare {
241+ workloads := make ([]kueue.Workload , 0 , len (wlList .Items ))
242+ for _ , wl := range wlList .Items {
243+ workloads = append (workloads , kueue.Workload {Status : kueue.WorkloadStatus {Admission : wl .Status .Admission }})
244+ }
245+ if diff := cmp .Diff (
246+ tc .wantWorkloads ,
247+ workloads ,
248+ cmpopts .EquateEmpty (),
249+ cmpopts .IgnoreFields (kueue.PodSetAssignment {}, "ResourceUsage" , "Count" ),
250+ ); diff != "" {
251+ t .Errorf ("Unexpected workloads (-want/+got)\n %s" , diff )
252+ }
253+ } else if diff := cmp .Diff (tc .wantWorkloads , wlList .Items , wlCmpOpts ... ); diff != "" {
211254 t .Errorf ("Unexpected workloads (-want/+got)\n %s" , diff )
212255 }
213256 })
214257 }
215258}
259+
260+ func TestResourceFlavorForResource (t * testing.T ) {
261+ cases := map [string ]struct {
262+ clusterQueue * kueue.ClusterQueue
263+ resource corev1.ResourceName
264+ wantFlavor kueue.ResourceFlavorReference
265+ }{
266+ "returns the flavor from the matching resource group" : {
267+ clusterQueue : utiltestingapi .MakeClusterQueue ("cq" ).
268+ ResourceGroup (
269+ * utiltestingapi .MakeFlavorQuotas ("cpu-flavor" ).
270+ Resource (corev1 .ResourceCPU , "10" , "0" ).
271+ Resource (corev1 .ResourceMemory , "10Gi" , "0" ).
272+ Obj (),
273+ ).
274+ ResourceGroup (
275+ * utiltestingapi .MakeFlavorQuotas ("gpu-flavor" ).
276+ Resource (corev1 .ResourceName ("nvidia.com/gpu" ), "10" , "0" ).
277+ Obj (),
278+ ).Obj (),
279+ resource : corev1 .ResourceName ("nvidia.com/gpu" ),
280+ wantFlavor : "gpu-flavor" ,
281+ },
282+ "returns the first flavor from the matching resource group" : {
283+ clusterQueue : utiltestingapi .MakeClusterQueue ("cq" ).
284+ ResourceGroup (
285+ * utiltestingapi .MakeFlavorQuotas ("on-demand" ).
286+ Resource (corev1 .ResourceName ("nvidia.com/gpu" ), "10" , "0" ).
287+ Obj (),
288+ * utiltestingapi .MakeFlavorQuotas ("spot" ).
289+ Resource (corev1 .ResourceName ("nvidia.com/gpu" ), "10" , "0" ).
290+ Obj (),
291+ ).Obj (),
292+ resource : corev1 .ResourceName ("nvidia.com/gpu" ),
293+ wantFlavor : "on-demand" ,
294+ },
295+ "returns an empty string when the resource is not covered by any resource group" : {
296+ clusterQueue : utiltestingapi .MakeClusterQueue ("cq" ).
297+ ResourceGroup (
298+ * utiltestingapi .MakeFlavorQuotas ("cpu-flavor" ).
299+ Resource (corev1 .ResourceCPU , "10" , "0" ).
300+ Obj (),
301+ ).Obj (),
302+ resource : corev1 .ResourceName ("nvidia.com/gpu" ),
303+ wantFlavor : "" ,
304+ },
305+ }
306+
307+ for name , tc := range cases {
308+ t .Run (name , func (t * testing.T ) {
309+ gotFlavor := resourceFlavorForResource (tc .clusterQueue , tc .resource )
310+
311+ if gotFlavor != tc .wantFlavor {
312+ t .Fatalf ("Unexpected flavor, got %q want %q" , gotFlavor , tc .wantFlavor )
313+ }
314+ })
315+ }
316+ }
0 commit comments