sigs.k8s.io/kueue@v0.6.2/pkg/config/validation.go (about) 1 /* 2 Copyright 2023 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 config 18 19 import ( 20 "fmt" 21 "slices" 22 23 corev1 "k8s.io/api/core/v1" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "k8s.io/apimachinery/pkg/apis/meta/v1/validation" 26 "k8s.io/apimachinery/pkg/labels" 27 "k8s.io/apimachinery/pkg/util/validation/field" 28 29 configapi "sigs.k8s.io/kueue/apis/config/v1beta1" 30 "sigs.k8s.io/kueue/pkg/constants" 31 ) 32 33 const ( 34 queueVisibilityClusterQueuesMaxValue = 4000 35 queueVisibilityClusterQueuesUpdateIntervalSeconds = 1 36 ) 37 38 var ( 39 integrationsPath = field.NewPath("integrations") 40 integrationsFrameworksPath = integrationsPath.Child("frameworks") 41 podOptionsPath = integrationsPath.Child("podOptions") 42 namespaceSelectorPath = podOptionsPath.Child("namespaceSelector") 43 waitForPodsReadyPath = field.NewPath("waitForPodsReady") 44 requeuingStrategyPath = waitForPodsReadyPath.Child("requeuingStrategy") 45 ) 46 47 func validate(c *configapi.Configuration) field.ErrorList { 48 var allErrs field.ErrorList 49 50 allErrs = append(allErrs, validateWaitForPodsReady(c)...) 51 52 allErrs = append(allErrs, validateQueueVisibility(c)...) 53 54 // Validate PodNamespaceSelector for the pod framework 55 allErrs = append(allErrs, validateIntegrations(c)...) 56 57 return allErrs 58 } 59 60 func validateWaitForPodsReady(c *configapi.Configuration) field.ErrorList { 61 var allErrs field.ErrorList 62 if !WaitForPodsReadyIsEnabled(c) { 63 return allErrs 64 } 65 if strategy := c.WaitForPodsReady.RequeuingStrategy; strategy != nil { 66 if strategy.Timestamp != nil && 67 *strategy.Timestamp != configapi.CreationTimestamp && *strategy.Timestamp != configapi.EvictionTimestamp { 68 allErrs = append(allErrs, field.NotSupported(requeuingStrategyPath.Child("timestamp"), 69 strategy.Timestamp, []configapi.RequeuingTimestamp{configapi.CreationTimestamp, configapi.EvictionTimestamp})) 70 } 71 if strategy.BackoffLimitCount != nil && *strategy.BackoffLimitCount < 0 { 72 allErrs = append(allErrs, field.Invalid(requeuingStrategyPath.Child("backoffLimitCount"), 73 *strategy.BackoffLimitCount, constants.IsNegativeErrorMsg)) 74 } 75 } 76 return allErrs 77 } 78 79 func validateQueueVisibility(cfg *configapi.Configuration) field.ErrorList { 80 var allErrs field.ErrorList 81 if cfg.QueueVisibility != nil { 82 queueVisibilityPath := field.NewPath("queueVisibility") 83 if cfg.QueueVisibility.ClusterQueues != nil { 84 clusterQueues := queueVisibilityPath.Child("clusterQueues") 85 if cfg.QueueVisibility.ClusterQueues.MaxCount > queueVisibilityClusterQueuesMaxValue { 86 allErrs = append(allErrs, field.Invalid(clusterQueues.Child("maxCount"), cfg.QueueVisibility.ClusterQueues.MaxCount, fmt.Sprintf("must be less than %d", queueVisibilityClusterQueuesMaxValue))) 87 } 88 } 89 if cfg.QueueVisibility.UpdateIntervalSeconds < queueVisibilityClusterQueuesUpdateIntervalSeconds { 90 allErrs = append(allErrs, field.Invalid(queueVisibilityPath.Child("updateIntervalSeconds"), cfg.QueueVisibility.UpdateIntervalSeconds, fmt.Sprintf("greater than or equal to %d", queueVisibilityClusterQueuesUpdateIntervalSeconds))) 91 } 92 } 93 return allErrs 94 } 95 96 func validateIntegrations(c *configapi.Configuration) field.ErrorList { 97 var allErrs field.ErrorList 98 99 if c.Integrations == nil { 100 return field.ErrorList{field.Required(integrationsPath, "cannot be empty")} 101 } 102 103 if c.Integrations.Frameworks == nil { 104 return field.ErrorList{field.Required(integrationsFrameworksPath, "cannot be empty")} 105 } 106 107 allErrs = append(allErrs, validatePodIntegrationOptions(c)...) 108 109 return allErrs 110 } 111 112 func validatePodIntegrationOptions(c *configapi.Configuration) field.ErrorList { 113 var allErrs field.ErrorList 114 115 if !slices.Contains(c.Integrations.Frameworks, "pod") { 116 return allErrs 117 } 118 119 if c.Integrations.PodOptions == nil { 120 return field.ErrorList{field.Required(podOptionsPath, "cannot be empty when pod integration is enabled")} 121 } 122 if c.Integrations.PodOptions.NamespaceSelector == nil { 123 return field.ErrorList{field.Required(namespaceSelectorPath, "a namespace selector is required")} 124 } 125 126 prohibitedNamespaces := []labels.Set{{corev1.LabelMetadataName: "kube-system"}} 127 128 if c.Namespace != nil && *c.Namespace != "" { 129 prohibitedNamespaces = append(prohibitedNamespaces, labels.Set{corev1.LabelMetadataName: *c.Namespace}) 130 } 131 132 allErrs = append(allErrs, validation.ValidateLabelSelector(c.Integrations.PodOptions.NamespaceSelector, validation.LabelSelectorValidationOptions{}, namespaceSelectorPath)...) 133 134 selector, err := metav1.LabelSelectorAsSelector(c.Integrations.PodOptions.NamespaceSelector) 135 if err != nil { 136 return allErrs 137 } 138 139 for _, pn := range prohibitedNamespaces { 140 if selector.Matches(pn) { 141 allErrs = append(allErrs, field.Invalid(namespaceSelectorPath, c.Integrations.PodOptions.NamespaceSelector, 142 fmt.Sprintf("should not match the %q namespace", pn[corev1.LabelMetadataName]))) 143 } 144 } 145 146 return allErrs 147 }