sigs.k8s.io/kueue@v0.6.2/pkg/webhooks/resourceflavor_webhook.go (about) 1 /* 2 Copyright 2022 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package webhooks 18 19 import ( 20 "context" 21 "strings" 22 23 corev1 "k8s.io/api/core/v1" 24 metavalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" 25 "k8s.io/apimachinery/pkg/runtime" 26 "k8s.io/apimachinery/pkg/util/sets" 27 "k8s.io/apimachinery/pkg/util/validation" 28 "k8s.io/apimachinery/pkg/util/validation/field" 29 "k8s.io/klog/v2" 30 ctrl "sigs.k8s.io/controller-runtime" 31 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 32 "sigs.k8s.io/controller-runtime/pkg/webhook" 33 "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 34 35 kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1" 36 ) 37 38 type ResourceFlavorWebhook struct{} 39 40 func setupWebhookForResourceFlavor(mgr ctrl.Manager) error { 41 return ctrl.NewWebhookManagedBy(mgr). 42 For(&kueue.ResourceFlavor{}). 43 WithDefaulter(&ResourceFlavorWebhook{}). 44 WithValidator(&ResourceFlavorWebhook{}). 45 Complete() 46 } 47 48 // +kubebuilder:webhook:path=/mutate-kueue-x-k8s-io-v1beta1-resourceflavor,mutating=true,failurePolicy=fail,sideEffects=None,groups=kueue.x-k8s.io,resources=resourceflavors,verbs=create,versions=v1beta1,name=mresourceflavor.kb.io,admissionReviewVersions=v1 49 50 var _ webhook.CustomDefaulter = &ResourceFlavorWebhook{} 51 52 // Default implements webhook.CustomDefaulter so a webhook will be registered for the type 53 func (w *ResourceFlavorWebhook) Default(ctx context.Context, obj runtime.Object) error { 54 rf := obj.(*kueue.ResourceFlavor) 55 log := ctrl.LoggerFrom(ctx).WithName("resourceflavor-webhook") 56 log.V(5).Info("Applying defaults", "resourceFlavor", klog.KObj(rf)) 57 58 if !controllerutil.ContainsFinalizer(rf, kueue.ResourceInUseFinalizerName) { 59 controllerutil.AddFinalizer(rf, kueue.ResourceInUseFinalizerName) 60 } 61 return nil 62 } 63 64 // +kubebuilder:webhook:path=/validate-kueue-x-k8s-io-v1beta1-resourceflavor,mutating=false,failurePolicy=fail,sideEffects=None,groups=kueue.x-k8s.io,resources=resourceflavors,verbs=create;update,versions=v1beta1,name=vresourceflavor.kb.io,admissionReviewVersions=v1 65 66 var _ webhook.CustomValidator = &ResourceFlavorWebhook{} 67 68 // ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type 69 func (w *ResourceFlavorWebhook) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { 70 rf := obj.(*kueue.ResourceFlavor) 71 log := ctrl.LoggerFrom(ctx).WithName("resourceflavor-webhook") 72 log.V(5).Info("Validating create", "resourceFlavor", klog.KObj(rf)) 73 return nil, ValidateResourceFlavor(rf).ToAggregate() 74 } 75 76 // ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type 77 func (w *ResourceFlavorWebhook) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { 78 newRF := newObj.(*kueue.ResourceFlavor) 79 log := ctrl.LoggerFrom(ctx).WithName("resourceflavor-webhook") 80 log.V(5).Info("Validating update", "resourceFlavor", klog.KObj(newRF)) 81 return nil, ValidateResourceFlavor(newRF).ToAggregate() 82 } 83 84 // ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type 85 func (w *ResourceFlavorWebhook) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { 86 return nil, nil 87 } 88 89 func ValidateResourceFlavor(rf *kueue.ResourceFlavor) field.ErrorList { 90 var allErrs field.ErrorList 91 92 specPath := field.NewPath("spec") 93 allErrs = append(allErrs, metavalidation.ValidateLabels(rf.Spec.NodeLabels, specPath.Child("nodeLabels"))...) 94 95 allErrs = append(allErrs, validateNodeTaints(rf.Spec.NodeTaints, specPath.Child("nodeTaints"))...) 96 allErrs = append(allErrs, validateTolerations(rf.Spec.Tolerations, specPath.Child("tolerations"))...) 97 return allErrs 98 } 99 100 // validateNodeTaints is extracted from git.k8s.io/kubernetes/pkg/apis/core/validation/validation.go 101 func validateNodeTaints(taints []corev1.Taint, fldPath *field.Path) field.ErrorList { 102 allErrors := field.ErrorList{} 103 104 uniqueTaints := make(map[corev1.TaintEffect]sets.Set[string]) 105 106 for i, currTaint := range taints { 107 idxPath := fldPath.Index(i) 108 // validate the taint key 109 allErrors = append(allErrors, metavalidation.ValidateLabelName(currTaint.Key, idxPath.Child("key"))...) 110 // validate the taint value 111 if errs := validation.IsValidLabelValue(currTaint.Value); len(errs) != 0 { 112 allErrors = append(allErrors, field.Invalid(idxPath.Child("value"), currTaint.Value, strings.Join(errs, ";"))) 113 } 114 // validate the taint effect 115 allErrors = append(allErrors, validateTaintEffect(&currTaint.Effect, false, idxPath.Child("effect"))...) 116 117 // validate if taint is unique by <key, effect> 118 if len(uniqueTaints[currTaint.Effect]) > 0 && uniqueTaints[currTaint.Effect].Has(currTaint.Key) { 119 duplicatedError := field.Duplicate(idxPath, currTaint) 120 duplicatedError.Detail = "taints must be unique by key and effect pair" 121 allErrors = append(allErrors, duplicatedError) 122 continue 123 } 124 125 // add taint to existingTaints for uniqueness check 126 if len(uniqueTaints[currTaint.Effect]) == 0 { 127 uniqueTaints[currTaint.Effect] = sets.New[string]() 128 } 129 uniqueTaints[currTaint.Effect].Insert(currTaint.Key) 130 } 131 return allErrors 132 } 133 134 // validateTaintEffect is extracted from git.k8s.io/kubernetes/pkg/apis/core/validation/validation.go 135 func validateTaintEffect(effect *corev1.TaintEffect, allowEmpty bool, fldPath *field.Path) field.ErrorList { 136 if !allowEmpty && len(*effect) == 0 { 137 return field.ErrorList{field.Required(fldPath, "")} 138 } 139 140 allErrors := field.ErrorList{} 141 switch *effect { 142 // TODO: Replace next line with subsequent commented-out line when implement TaintEffectNoScheduleNoAdmit. 143 case corev1.TaintEffectNoSchedule, corev1.TaintEffectPreferNoSchedule, corev1.TaintEffectNoExecute: 144 // case core.TaintEffectNoSchedule, core.TaintEffectPreferNoSchedule, core.TaintEffectNoScheduleNoAdmit, core.TaintEffectNoExecute: 145 default: 146 validValues := []string{ 147 string(corev1.TaintEffectNoSchedule), 148 string(corev1.TaintEffectPreferNoSchedule), 149 string(corev1.TaintEffectNoExecute), 150 // TODO: Uncomment this block when implement TaintEffectNoScheduleNoAdmit. 151 // string(core.TaintEffectNoScheduleNoAdmit), 152 } 153 allErrors = append(allErrors, field.NotSupported(fldPath, *effect, validValues)) 154 } 155 return allErrors 156 }