@@ -28,21 +28,28 @@ import (
2828 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2929 "k8s.io/apimachinery/pkg/runtime"
3030 "k8s.io/apimachinery/pkg/types"
31+
3132 ctrl "sigs.k8s.io/controller-runtime"
3233 "sigs.k8s.io/controller-runtime/pkg/client"
3334 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3435 "sigs.k8s.io/controller-runtime/pkg/handler"
3536 "sigs.k8s.io/controller-runtime/pkg/log"
3637 "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"
3742 "sigs.k8s.io/kueue/pkg/podset"
3843 utilmaps "sigs.k8s.io/kueue/pkg/util/maps"
44+ "sigs.k8s.io/kueue/pkg/workload"
3945
4046 workloadv1beta2 "github.com/project-codeflare/appwrapper/api/v1beta2"
4147)
4248
4349const (
4450 AppWrapperLabel = "workload.codeflare.dev/appwrapper"
4551 appWrapperFinalizer = "workload.codeflare.dev/finalizer"
52+ childJobQueueName = "workload.codeflare.dev.admitted"
4653)
4754
4855// AppWrapperReconciler reconciles an appwrapper
@@ -212,6 +219,7 @@ func (r *AppWrapperReconciler) Reconcile(ctx context.Context, req ctrl.Request)
212219 })
213220 return ctrl.Result {RequeueAfter : time .Minute }, r .Status ().Update (ctx , aw )
214221 }
222+ r .propagateAdmission (ctx , aw )
215223 meta .SetStatusCondition (& aw .Status .Conditions , metav1.Condition {
216224 Type : string (workloadv1beta2 .PodsReady ),
217225 Status : metav1 .ConditionFalse ,
@@ -266,14 +274,6 @@ func (r *AppWrapperReconciler) Reconcile(ctx context.Context, req ctrl.Request)
266274 return ctrl.Result {}, nil
267275}
268276
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-
277277// podMapFunc maps pods to appwrappers
278278func (r * AppWrapperReconciler ) podMapFunc (ctx context.Context , obj client.Object ) []reconcile.Request {
279279 pod := obj .(* v1.Pod )
@@ -299,7 +299,8 @@ func parseComponent(aw *workloadv1beta2.AppWrapper, raw []byte) (*unstructured.U
299299 return obj , nil
300300}
301301
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 ]
303304 toMap := func (x interface {}) map [string ]string {
304305 if x == nil {
305306 return nil
@@ -322,19 +323,20 @@ func materializeObject(aw *workloadv1beta2.AppWrapper, component *workloadv1beta
322323 }
323324 }
324325 }
325- awLabels := map [string ]string {AppWrapperLabel : aw .Name }
326326
327327 obj , err := parseComponent (aw , component .Template .Raw )
328328 if err != nil {
329- return nil , err
329+ return nil , err , true
330330 }
331+ obj .SetLabels (utilmaps .MergeKeepFirst (obj .GetLabels (), map [string ]string {AppWrapperLabel : aw .Name , constants .QueueLabel : childJobQueueName }))
331332
333+ awLabels := map [string ]string {AppWrapperLabel : aw .Name }
332334 for podSetsIdx , podSet := range component .PodSets {
333335 toInject := component .PodSetInfos [podSetsIdx ]
334336
335337 p , err := getRawTemplate (obj .UnstructuredContent (), podSet .Path )
336338 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
338340 }
339341 if md , ok := p ["metadata" ]; ! ok || md == nil {
340342 p ["metadata" ] = make (map [string ]interface {})
@@ -346,7 +348,7 @@ func materializeObject(aw *workloadv1beta2.AppWrapper, component *workloadv1beta
346348 if len (toInject .Annotations ) > 0 {
347349 existing := toMap (metadata ["annotations" ])
348350 if err := utilmaps .HaveConflict (existing , toInject .Annotations ); err != nil {
349- return nil , podset .BadPodSetsUpdateError ("annotations" , err )
351+ return nil , podset .BadPodSetsUpdateError ("annotations" , err ), true
350352 }
351353 metadata ["annotations" ] = utilmaps .MergeKeepFirst (existing , toInject .Annotations )
352354 }
@@ -355,15 +357,15 @@ func materializeObject(aw *workloadv1beta2.AppWrapper, component *workloadv1beta
355357 mergedLabels := utilmaps .MergeKeepFirst (toInject .Labels , awLabels )
356358 existing := toMap (metadata ["labels" ])
357359 if err := utilmaps .HaveConflict (existing , mergedLabels ); err != nil {
358- return nil , podset .BadPodSetsUpdateError ("labels" , err )
360+ return nil , podset .BadPodSetsUpdateError ("labels" , err ), true
359361 }
360362 metadata ["labels" ] = utilmaps .MergeKeepFirst (existing , mergedLabels )
361363
362364 // NodeSelectors
363365 if len (toInject .NodeSelector ) > 0 {
364366 existing := toMap (metadata ["nodeSelector" ])
365367 if err := utilmaps .HaveConflict (existing , toInject .NodeSelector ); err != nil {
366- return nil , podset .BadPodSetsUpdateError ("nodeSelector" , err )
368+ return nil , podset .BadPodSetsUpdateError ("nodeSelector" , err ), true
367369 }
368370 metadata ["nodeSelector" ] = utilmaps .MergeKeepFirst (existing , toInject .NodeSelector )
369371 }
@@ -381,27 +383,57 @@ func materializeObject(aw *workloadv1beta2.AppWrapper, component *workloadv1beta
381383 }
382384 }
383385
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
385397}
386398
387399func (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 )
390402 if err != nil {
391- return err , true
403+ return err , fatal
392404 }
405+ }
406+ return nil , false
407+ }
393408
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+ }
400434 }
401- return err , meta .IsNoMatchError (err ) || apierrors .IsInvalid (err ) // fatal
402435 }
403436 }
404- return nil , false
405437}
406438
407439func (r * AppWrapperReconciler ) deleteComponents (ctx context.Context , aw * workloadv1beta2.AppWrapper ) bool {
@@ -476,3 +508,11 @@ func ExpectedPodCount(aw *workloadv1beta2.AppWrapper) int32 {
476508 }
477509 return expected
478510}
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