@@ -28,21 +28,28 @@ import (
28
28
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
29
29
"k8s.io/apimachinery/pkg/runtime"
30
30
"k8s.io/apimachinery/pkg/types"
31
+
31
32
ctrl "sigs.k8s.io/controller-runtime"
32
33
"sigs.k8s.io/controller-runtime/pkg/client"
33
34
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
34
35
"sigs.k8s.io/controller-runtime/pkg/handler"
35
36
"sigs.k8s.io/controller-runtime/pkg/log"
36
37
"sigs.k8s.io/controller-runtime/pkg/reconcile"
38
+
39
+ kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1"
40
+ "sigs.k8s.io/kueue/pkg/controller/constants"
41
+ "sigs.k8s.io/kueue/pkg/controller/jobframework"
37
42
"sigs.k8s.io/kueue/pkg/podset"
38
43
utilmaps "sigs.k8s.io/kueue/pkg/util/maps"
44
+ "sigs.k8s.io/kueue/pkg/workload"
39
45
40
46
workloadv1beta2 "github.com/project-codeflare/appwrapper/api/v1beta2"
41
47
)
42
48
43
49
const (
44
50
AppWrapperLabel = "workload.codeflare.dev/appwrapper"
45
51
appWrapperFinalizer = "workload.codeflare.dev/finalizer"
52
+ childJobQueueName = "workload.codeflare.dev.admitted"
46
53
)
47
54
48
55
// AppWrapperReconciler reconciles an appwrapper
@@ -212,6 +219,7 @@ func (r *AppWrapperReconciler) Reconcile(ctx context.Context, req ctrl.Request)
212
219
})
213
220
return ctrl.Result {RequeueAfter : time .Minute }, r .Status ().Update (ctx , aw )
214
221
}
222
+ r .propagateAdmission (ctx , aw )
215
223
meta .SetStatusCondition (& aw .Status .Conditions , metav1.Condition {
216
224
Type : string (workloadv1beta2 .PodsReady ),
217
225
Status : metav1 .ConditionFalse ,
@@ -266,14 +274,6 @@ func (r *AppWrapperReconciler) Reconcile(ctx context.Context, req ctrl.Request)
266
274
return ctrl.Result {}, nil
267
275
}
268
276
269
- // SetupWithManager sets up the controller with the Manager.
270
- func (r * AppWrapperReconciler ) SetupWithManager (mgr ctrl.Manager ) error {
271
- return ctrl .NewControllerManagedBy (mgr ).
272
- For (& workloadv1beta2.AppWrapper {}).
273
- Watches (& v1.Pod {}, handler .EnqueueRequestsFromMapFunc (r .podMapFunc )).
274
- Complete (r )
275
- }
276
-
277
277
// podMapFunc maps pods to appwrappers
278
278
func (r * AppWrapperReconciler ) podMapFunc (ctx context.Context , obj client.Object ) []reconcile.Request {
279
279
pod := obj .(* v1.Pod )
@@ -299,7 +299,8 @@ func parseComponent(aw *workloadv1beta2.AppWrapper, raw []byte) (*unstructured.U
299
299
return obj , nil
300
300
}
301
301
302
- func materializeObject (aw * workloadv1beta2.AppWrapper , component * workloadv1beta2.AppWrapperComponent ) (client.Object , error ) {
302
+ func (r * AppWrapperReconciler ) createComponent (ctx context.Context , aw * workloadv1beta2.AppWrapper , componentIdx int ) (* unstructured.Unstructured , error , bool ) {
303
+ component := aw .Spec .Components [componentIdx ]
303
304
toMap := func (x interface {}) map [string ]string {
304
305
if x == nil {
305
306
return nil
@@ -322,19 +323,20 @@ func materializeObject(aw *workloadv1beta2.AppWrapper, component *workloadv1beta
322
323
}
323
324
}
324
325
}
325
- awLabels := map [string ]string {AppWrapperLabel : aw .Name }
326
326
327
327
obj , err := parseComponent (aw , component .Template .Raw )
328
328
if err != nil {
329
- return nil , err
329
+ return nil , err , true
330
330
}
331
+ obj .SetLabels (utilmaps .MergeKeepFirst (obj .GetLabels (), map [string ]string {AppWrapperLabel : aw .Name , constants .QueueLabel : childJobQueueName }))
331
332
333
+ awLabels := map [string ]string {AppWrapperLabel : aw .Name }
332
334
for podSetsIdx , podSet := range component .PodSets {
333
335
toInject := component .PodSetInfos [podSetsIdx ]
334
336
335
337
p , err := getRawTemplate (obj .UnstructuredContent (), podSet .Path )
336
338
if err != nil {
337
- return nil , err // Should not happen, path validity is enforced by validateAppWrapperInvariants
339
+ return nil , err , true // Should not happen, path validity is enforced by validateAppWrapperInvariants
338
340
}
339
341
if md , ok := p ["metadata" ]; ! ok || md == nil {
340
342
p ["metadata" ] = make (map [string ]interface {})
@@ -346,7 +348,7 @@ func materializeObject(aw *workloadv1beta2.AppWrapper, component *workloadv1beta
346
348
if len (toInject .Annotations ) > 0 {
347
349
existing := toMap (metadata ["annotations" ])
348
350
if err := utilmaps .HaveConflict (existing , toInject .Annotations ); err != nil {
349
- return nil , podset .BadPodSetsUpdateError ("annotations" , err )
351
+ return nil , podset .BadPodSetsUpdateError ("annotations" , err ), true
350
352
}
351
353
metadata ["annotations" ] = utilmaps .MergeKeepFirst (existing , toInject .Annotations )
352
354
}
@@ -355,15 +357,15 @@ func materializeObject(aw *workloadv1beta2.AppWrapper, component *workloadv1beta
355
357
mergedLabels := utilmaps .MergeKeepFirst (toInject .Labels , awLabels )
356
358
existing := toMap (metadata ["labels" ])
357
359
if err := utilmaps .HaveConflict (existing , mergedLabels ); err != nil {
358
- return nil , podset .BadPodSetsUpdateError ("labels" , err )
360
+ return nil , podset .BadPodSetsUpdateError ("labels" , err ), true
359
361
}
360
362
metadata ["labels" ] = utilmaps .MergeKeepFirst (existing , mergedLabels )
361
363
362
364
// NodeSelectors
363
365
if len (toInject .NodeSelector ) > 0 {
364
366
existing := toMap (metadata ["nodeSelector" ])
365
367
if err := utilmaps .HaveConflict (existing , toInject .NodeSelector ); err != nil {
366
- return nil , podset .BadPodSetsUpdateError ("nodeSelector" , err )
368
+ return nil , podset .BadPodSetsUpdateError ("nodeSelector" , err ), true
367
369
}
368
370
metadata ["nodeSelector" ] = utilmaps .MergeKeepFirst (existing , toInject .NodeSelector )
369
371
}
@@ -381,27 +383,57 @@ func materializeObject(aw *workloadv1beta2.AppWrapper, component *workloadv1beta
381
383
}
382
384
}
383
385
384
- return obj , nil
386
+ if err := controllerutil .SetControllerReference (aw , obj , r .Scheme ); err != nil {
387
+ return nil , err , true
388
+ }
389
+
390
+ if err := r .Create (ctx , obj ); err != nil {
391
+ if ! apierrors .IsAlreadyExists (err ) {
392
+ return nil , err , meta .IsNoMatchError (err ) || apierrors .IsInvalid (err ) // fatal
393
+ }
394
+ }
395
+
396
+ return obj , nil , false
385
397
}
386
398
387
399
func (r * AppWrapperReconciler ) createComponents (ctx context.Context , aw * workloadv1beta2.AppWrapper ) (error , bool ) {
388
- for _ , component := range aw .Spec .Components {
389
- obj , err := materializeObject ( aw , & component )
400
+ for componentIdx := range aw .Spec .Components {
401
+ _ , err , fatal := r . createComponent ( ctx , aw , componentIdx )
390
402
if err != nil {
391
- return err , true
403
+ return err , fatal
392
404
}
405
+ }
406
+ return nil , false
407
+ }
393
408
394
- if err := controllerutil .SetControllerReference (aw , obj , r .Scheme ); err != nil {
395
- return err , true
396
- }
397
- if err := r .Create (ctx , obj ); err != nil {
398
- if apierrors .IsAlreadyExists (err ) {
399
- continue // ignore existing component
409
+ func (r * AppWrapperReconciler ) propagateAdmission (ctx context.Context , aw * workloadv1beta2.AppWrapper ) {
410
+ for componentIdx , component := range aw .Spec .Components {
411
+ if len (component .PodSets ) > 0 {
412
+ obj , err := parseComponent (aw , component .Template .Raw )
413
+ if err != nil {
414
+ return
415
+ }
416
+ wlName := jobframework .GetWorkloadNameForOwnerWithGVK (obj .GetName (), obj .GroupVersionKind ())
417
+ wl := & kueue.Workload {}
418
+ if err := r .Client .Get (ctx , client.ObjectKey {Namespace : aw .Namespace , Name : wlName }, wl ); err == nil {
419
+ if ! workload .IsAdmitted (wl ) {
420
+ admission := kueue.Admission {
421
+ ClusterQueue : childJobQueueName ,
422
+ PodSetAssignments : make ([]kueue.PodSetAssignment , len (aw .Spec .Components [componentIdx ].PodSets )),
423
+ }
424
+ for i := range admission .PodSetAssignments {
425
+ admission .PodSetAssignments [i ].Name = wl .Spec .PodSets [i ].Name
426
+ }
427
+ newWorkload := wl .DeepCopy ()
428
+ workload .SetQuotaReservation (newWorkload , & admission )
429
+ _ = workload .SyncAdmittedCondition (newWorkload )
430
+ if err = workload .ApplyAdmissionStatus (ctx , r .Client , newWorkload , false ); err != nil {
431
+ log .FromContext (ctx ).Error (err , "syncing admission" , "appwrapper" , aw , "componentIdx" , componentIdx , "workload" , wl , "newworkload" , newWorkload )
432
+ }
433
+ }
400
434
}
401
- return err , meta .IsNoMatchError (err ) || apierrors .IsInvalid (err ) // fatal
402
435
}
403
436
}
404
- return nil , false
405
437
}
406
438
407
439
func (r * AppWrapperReconciler ) deleteComponents (ctx context.Context , aw * workloadv1beta2.AppWrapper ) bool {
@@ -476,3 +508,11 @@ func ExpectedPodCount(aw *workloadv1beta2.AppWrapper) int32 {
476
508
}
477
509
return expected
478
510
}
511
+
512
+ // SetupWithManager sets up the controller with the Manager.
513
+ func (r * AppWrapperReconciler ) SetupWithManager (mgr ctrl.Manager ) error {
514
+ return ctrl .NewControllerManagedBy (mgr ).
515
+ For (& workloadv1beta2.AppWrapper {}).
516
+ Watches (& v1.Pod {}, handler .EnqueueRequestsFromMapFunc (r .podMapFunc )).
517
+ Complete (r )
518
+ }
0 commit comments