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  }