sigs.k8s.io/kueue@v0.6.2/pkg/podset/podset.go (about) 1 /* 2 Copyright 2023 The Kubernetes Authors. 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 http://www.apache.org/licenses/LICENSE-2.0 7 Unless required by applicable law or agreed to in writing, software 8 distributed under the License is distributed on an "AS IS" BASIS, 9 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 See the License for the specific language governing permissions and 11 limitations under the License. 12 */ 13 14 package podset 15 16 import ( 17 "context" 18 "errors" 19 "fmt" 20 "maps" 21 "slices" 22 23 corev1 "k8s.io/api/core/v1" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "k8s.io/apimachinery/pkg/types" 26 "k8s.io/apimachinery/pkg/util/sets" 27 "k8s.io/utils/ptr" 28 "sigs.k8s.io/controller-runtime/pkg/client" 29 30 kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1" 31 utilmaps "sigs.k8s.io/kueue/pkg/util/maps" 32 ) 33 34 var ( 35 ErrInvalidPodsetInfo = errors.New("invalid podset infos") 36 ErrInvalidPodSetUpdate = errors.New("invalid admission check PodSetUpdate") 37 ) 38 39 type PodSetInfo struct { 40 Name string 41 Count int32 42 Annotations map[string]string 43 Labels map[string]string 44 NodeSelector map[string]string 45 Tolerations []corev1.Toleration 46 } 47 48 // FromAssignment returns a PodSetInfo based on the provided assignment and an error if unable 49 // to get any of the referenced flavors. 50 func FromAssignment(ctx context.Context, client client.Client, assignment *kueue.PodSetAssignment, defaultCount int32) (PodSetInfo, error) { 51 processedFlvs := sets.New[kueue.ResourceFlavorReference]() 52 info := PodSetInfo{ 53 Name: assignment.Name, 54 NodeSelector: make(map[string]string), 55 Count: ptr.Deref(assignment.Count, defaultCount), 56 Labels: make(map[string]string), 57 Annotations: make(map[string]string), 58 } 59 for _, flvRef := range assignment.Flavors { 60 if processedFlvs.Has(flvRef) { 61 continue 62 } 63 // Lookup the ResourceFlavors to fetch the node affinity labels and toleration to apply on the job. 64 flv := kueue.ResourceFlavor{} 65 if err := client.Get(ctx, types.NamespacedName{Name: string(flvRef)}, &flv); err != nil { 66 return info, err 67 } 68 info.NodeSelector = utilmaps.MergeKeepFirst(info.NodeSelector, flv.Spec.NodeLabels) 69 info.Tolerations = append(info.Tolerations, flv.Spec.Tolerations...) 70 71 processedFlvs.Insert(flvRef) 72 } 73 return info, nil 74 } 75 76 // FromUpdate returns a PodSetInfo based on the provided PodSetUpdate 77 func FromUpdate(update *kueue.PodSetUpdate) PodSetInfo { 78 return PodSetInfo{ 79 Annotations: update.Annotations, 80 Labels: update.Labels, 81 NodeSelector: update.NodeSelector, 82 Tolerations: update.Tolerations, 83 } 84 } 85 86 // FromPodSet returns a PodSeeInfo based on the provided PodSet 87 func FromPodSet(ps *kueue.PodSet) PodSetInfo { 88 return PodSetInfo{ 89 Name: ps.Name, 90 Count: ps.Count, 91 Annotations: maps.Clone(ps.Template.Annotations), 92 Labels: maps.Clone(ps.Template.Labels), 93 NodeSelector: maps.Clone(ps.Template.Spec.NodeSelector), 94 Tolerations: slices.Clone(ps.Template.Spec.Tolerations), 95 } 96 } 97 98 func (podSetInfo *PodSetInfo) Merge(o PodSetInfo) error { 99 if err := utilmaps.HaveConflict(podSetInfo.Annotations, o.Annotations); err != nil { 100 return BadPodSetsUpdateError("annotations", err) 101 } 102 if err := utilmaps.HaveConflict(podSetInfo.Labels, o.Labels); err != nil { 103 return BadPodSetsUpdateError("labels", err) 104 } 105 if err := utilmaps.HaveConflict(podSetInfo.NodeSelector, o.NodeSelector); err != nil { 106 return BadPodSetsUpdateError("nodeSelector", err) 107 } 108 podSetInfo.Annotations = utilmaps.MergeKeepFirst(podSetInfo.Annotations, o.Annotations) 109 podSetInfo.Labels = utilmaps.MergeKeepFirst(podSetInfo.Labels, o.Labels) 110 podSetInfo.NodeSelector = utilmaps.MergeKeepFirst(podSetInfo.NodeSelector, o.NodeSelector) 111 podSetInfo.Tolerations = append(podSetInfo.Tolerations, o.Tolerations...) 112 return nil 113 } 114 115 // AddOrUpdateLabel adds or updates the label identified by k with value v 116 // allocating a new Labels nap if nil 117 func (podSetInfo *PodSetInfo) AddOrUpdateLabel(k, v string) { 118 if podSetInfo.Labels == nil { 119 podSetInfo.Labels = map[string]string{k: v} 120 } else { 121 podSetInfo.Labels[k] = v 122 } 123 } 124 125 // Merge updates or appends the replica metadata & spec fields based on PodSetInfo. 126 // It returns error if there is a conflict. 127 func Merge(meta *metav1.ObjectMeta, spec *corev1.PodSpec, info PodSetInfo) error { 128 tmp := PodSetInfo{ 129 Annotations: meta.Annotations, 130 Labels: meta.Labels, 131 NodeSelector: spec.NodeSelector, 132 Tolerations: spec.Tolerations, 133 } 134 if err := tmp.Merge(info); err != nil { 135 return err 136 } 137 meta.Annotations = tmp.Annotations 138 meta.Labels = tmp.Labels 139 spec.NodeSelector = tmp.NodeSelector 140 spec.Tolerations = tmp.Tolerations 141 return nil 142 } 143 144 // RestorePodSpec sets replica metadata and spec fields based on PodSetInfo. 145 // It returns true if there is any change. 146 func RestorePodSpec(meta *metav1.ObjectMeta, spec *corev1.PodSpec, info PodSetInfo) bool { 147 changed := false 148 if !maps.Equal(meta.Annotations, info.Annotations) { 149 meta.Annotations = maps.Clone(info.Annotations) 150 changed = true 151 } 152 if !maps.Equal(meta.Labels, info.Labels) { 153 meta.Labels = maps.Clone(info.Labels) 154 changed = true 155 } 156 if !maps.Equal(spec.NodeSelector, info.NodeSelector) { 157 spec.NodeSelector = maps.Clone(info.NodeSelector) 158 changed = true 159 } 160 if !slices.Equal(spec.Tolerations, info.Tolerations) { 161 spec.Tolerations = slices.Clone(info.Tolerations) 162 changed = true 163 } 164 return changed 165 } 166 167 func BadPodSetsInfoLenError(want, got int) error { 168 return fmt.Errorf("%w: expecting %d podset, got %d", ErrInvalidPodsetInfo, got, want) 169 } 170 171 func BadPodSetsUpdateError(update string, err error) error { 172 return fmt.Errorf("%w: conflict for %v: %v", ErrInvalidPodSetUpdate, update, err) 173 } 174 175 func IsPermanent(e error) bool { 176 return errors.Is(e, ErrInvalidPodsetInfo) || errors.Is(e, ErrInvalidPodSetUpdate) 177 }