sigs.k8s.io/kueue@v0.6.2/pkg/config/validation_test.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  	"testing"
    22  
    23  	"github.com/google/go-cmp/cmp"
    24  	"github.com/google/go-cmp/cmp/cmpopts"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/util/validation/field"
    27  	"k8s.io/utils/ptr"
    28  
    29  	configapi "sigs.k8s.io/kueue/apis/config/v1beta1"
    30  )
    31  
    32  func TestValidate(t *testing.T) {
    33  	defaultQueueVisibility := &configapi.QueueVisibility{
    34  		UpdateIntervalSeconds: configapi.DefaultQueueVisibilityUpdateIntervalSeconds,
    35  		ClusterQueues: &configapi.ClusterQueueVisibility{
    36  			MaxCount: configapi.DefaultClusterQueuesMaxCount,
    37  		},
    38  	}
    39  
    40  	defaultPodIntegrationOptions := &configapi.PodIntegrationOptions{
    41  		NamespaceSelector: &metav1.LabelSelector{
    42  			MatchExpressions: []metav1.LabelSelectorRequirement{
    43  				{
    44  					Key:      "kubernetes.io/metadata.name",
    45  					Operator: metav1.LabelSelectorOpNotIn,
    46  					Values:   []string{"kube-system", "kueue-system"},
    47  				},
    48  			},
    49  		},
    50  		PodSelector: &metav1.LabelSelector{},
    51  	}
    52  
    53  	defaultIntegrations := &configapi.Integrations{
    54  		Frameworks: []string{"batch/job"},
    55  		PodOptions: defaultPodIntegrationOptions,
    56  	}
    57  
    58  	testCases := map[string]struct {
    59  		cfg     *configapi.Configuration
    60  		wantErr field.ErrorList
    61  	}{
    62  		"empty": {
    63  			cfg: &configapi.Configuration{},
    64  			wantErr: field.ErrorList{
    65  				&field.Error{
    66  					Type:  field.ErrorTypeRequired,
    67  					Field: "integrations",
    68  				},
    69  			},
    70  		},
    71  		"invalid queue visibility UpdateIntervalSeconds": {
    72  			cfg: &configapi.Configuration{
    73  				QueueVisibility: &configapi.QueueVisibility{
    74  					UpdateIntervalSeconds: 0,
    75  				},
    76  				Integrations: defaultIntegrations,
    77  			},
    78  			wantErr: field.ErrorList{
    79  				field.Invalid(field.NewPath("queueVisibility").Child("updateIntervalSeconds"), 0, fmt.Sprintf("greater than or equal to %d", queueVisibilityClusterQueuesUpdateIntervalSeconds)),
    80  			},
    81  		},
    82  		"invalid queue visibility cluster queue max count": {
    83  			cfg: &configapi.Configuration{
    84  				QueueVisibility: &configapi.QueueVisibility{
    85  					ClusterQueues: &configapi.ClusterQueueVisibility{
    86  						MaxCount: 4001,
    87  					},
    88  					UpdateIntervalSeconds: 1,
    89  				},
    90  				Integrations: defaultIntegrations,
    91  			},
    92  			wantErr: field.ErrorList{
    93  				field.Invalid(field.NewPath("queueVisibility").Child("clusterQueues").Child("maxCount"), 4001, fmt.Sprintf("must be less than %d", queueVisibilityClusterQueuesMaxValue)),
    94  			},
    95  		},
    96  		"nil PodIntegrationOptions": {
    97  			cfg: &configapi.Configuration{
    98  				QueueVisibility: defaultQueueVisibility,
    99  				Integrations: &configapi.Integrations{
   100  					Frameworks: []string{"pod"},
   101  					PodOptions: nil,
   102  				},
   103  			},
   104  			wantErr: field.ErrorList{
   105  				&field.Error{
   106  					Type:  field.ErrorTypeRequired,
   107  					Field: "integrations.podOptions",
   108  				},
   109  			},
   110  		},
   111  		"nil PodIntegrationOptions.NamespaceSelector": {
   112  			cfg: &configapi.Configuration{
   113  				QueueVisibility: defaultQueueVisibility,
   114  				Integrations: &configapi.Integrations{
   115  					Frameworks: []string{"pod"},
   116  					PodOptions: &configapi.PodIntegrationOptions{
   117  						NamespaceSelector: nil,
   118  					},
   119  				},
   120  			},
   121  			wantErr: field.ErrorList{
   122  				&field.Error{
   123  					Type:  field.ErrorTypeRequired,
   124  					Field: "integrations.podOptions.namespaceSelector",
   125  				},
   126  			},
   127  		},
   128  		"emptyLabelSelector": {
   129  			cfg: &configapi.Configuration{
   130  				Namespace:       ptr.To("kueue-system"),
   131  				QueueVisibility: defaultQueueVisibility,
   132  				Integrations: &configapi.Integrations{
   133  					Frameworks: []string{"pod"},
   134  					PodOptions: &configapi.PodIntegrationOptions{
   135  						NamespaceSelector: &metav1.LabelSelector{},
   136  					},
   137  				},
   138  			},
   139  			wantErr: field.ErrorList{
   140  				&field.Error{
   141  					Type:  field.ErrorTypeInvalid,
   142  					Field: "integrations.podOptions.namespaceSelector",
   143  				},
   144  				&field.Error{
   145  					Type:  field.ErrorTypeInvalid,
   146  					Field: "integrations.podOptions.namespaceSelector",
   147  				},
   148  			},
   149  		},
   150  		"prohibited namespace in MatchLabels": {
   151  			cfg: &configapi.Configuration{
   152  				QueueVisibility: defaultQueueVisibility,
   153  				Integrations: &configapi.Integrations{
   154  					Frameworks: []string{"pod"},
   155  					PodOptions: &configapi.PodIntegrationOptions{
   156  						NamespaceSelector: &metav1.LabelSelector{
   157  							MatchLabels: map[string]string{
   158  								"kubernetes.io/metadata.name": "kube-system",
   159  							},
   160  						},
   161  					},
   162  				},
   163  			},
   164  			wantErr: field.ErrorList{
   165  				&field.Error{
   166  					Type:  field.ErrorTypeInvalid,
   167  					Field: "integrations.podOptions.namespaceSelector",
   168  				},
   169  			},
   170  		},
   171  		"prohibited namespace in MatchExpressions with operator In": {
   172  			cfg: &configapi.Configuration{
   173  				QueueVisibility: defaultQueueVisibility,
   174  				Integrations: &configapi.Integrations{
   175  					Frameworks: []string{"pod"},
   176  					PodOptions: &configapi.PodIntegrationOptions{
   177  						NamespaceSelector: &metav1.LabelSelector{
   178  							MatchExpressions: []metav1.LabelSelectorRequirement{
   179  								{
   180  									Key:      "kubernetes.io/metadata.name",
   181  									Operator: metav1.LabelSelectorOpIn,
   182  									Values:   []string{"kube-system"},
   183  								},
   184  							},
   185  						},
   186  					},
   187  				},
   188  			},
   189  			wantErr: field.ErrorList{
   190  				&field.Error{
   191  					Type:  field.ErrorTypeInvalid,
   192  					Field: "integrations.podOptions.namespaceSelector",
   193  				},
   194  			},
   195  		},
   196  		"prohibited namespace in MatchExpressions with operator NotIn": {
   197  			cfg: &configapi.Configuration{
   198  				QueueVisibility: defaultQueueVisibility,
   199  				Integrations: &configapi.Integrations{
   200  					Frameworks: []string{"pod"},
   201  					PodOptions: &configapi.PodIntegrationOptions{
   202  						NamespaceSelector: &metav1.LabelSelector{
   203  							MatchExpressions: []metav1.LabelSelectorRequirement{
   204  								{
   205  									Key:      "kubernetes.io/metadata.name",
   206  									Operator: metav1.LabelSelectorOpNotIn,
   207  									Values:   []string{"kube-system", "kueue-system"},
   208  								},
   209  							},
   210  						},
   211  					},
   212  				},
   213  			},
   214  			wantErr: nil,
   215  		},
   216  		"no supported waitForPodsReady.requeuingStrategy.timestamp": {
   217  			cfg: &configapi.Configuration{
   218  				Integrations: defaultIntegrations,
   219  				WaitForPodsReady: &configapi.WaitForPodsReady{
   220  					Enable: true,
   221  					RequeuingStrategy: &configapi.RequeuingStrategy{
   222  						Timestamp: ptr.To[configapi.RequeuingTimestamp]("NoSupported"),
   223  					},
   224  				},
   225  			},
   226  			wantErr: field.ErrorList{
   227  				&field.Error{
   228  					Type:  field.ErrorTypeNotSupported,
   229  					Field: "waitForPodsReady.requeuingStrategy.timestamp",
   230  				},
   231  			},
   232  		},
   233  		"supported waitForPodsReady.requeuingStrategy.timestamp": {
   234  			cfg: &configapi.Configuration{
   235  				Integrations: defaultIntegrations,
   236  				WaitForPodsReady: &configapi.WaitForPodsReady{
   237  					Enable: true,
   238  					RequeuingStrategy: &configapi.RequeuingStrategy{
   239  						Timestamp: ptr.To(configapi.CreationTimestamp),
   240  					},
   241  				},
   242  			},
   243  			wantErr: nil,
   244  		},
   245  		"non-negative waitForPodsReady.requeuingStrategy.backoffLimitCount": {
   246  			cfg: &configapi.Configuration{
   247  				Integrations: defaultIntegrations,
   248  				WaitForPodsReady: &configapi.WaitForPodsReady{
   249  					Enable: true,
   250  					RequeuingStrategy: &configapi.RequeuingStrategy{
   251  						BackoffLimitCount: ptr.To[int32](10),
   252  					},
   253  				},
   254  			},
   255  			wantErr: nil,
   256  		},
   257  		"negative waitForPodsReady.requeuingStrategy.backoffLimitCount": {
   258  			cfg: &configapi.Configuration{
   259  				Integrations: defaultIntegrations,
   260  				WaitForPodsReady: &configapi.WaitForPodsReady{
   261  					Enable: true,
   262  					RequeuingStrategy: &configapi.RequeuingStrategy{
   263  						BackoffLimitCount: ptr.To[int32](-1),
   264  					},
   265  				},
   266  			},
   267  			wantErr: field.ErrorList{
   268  				&field.Error{
   269  					Type:  field.ErrorTypeInvalid,
   270  					Field: "waitForPodsReady.requeuingStrategy.backoffLimitCount",
   271  				},
   272  			},
   273  		},
   274  	}
   275  
   276  	for name, tc := range testCases {
   277  		t.Run(name, func(t *testing.T) {
   278  			if diff := cmp.Diff(tc.wantErr, validate(tc.cfg), cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail")); diff != "" {
   279  				t.Errorf("Unexpected returned error (-want,+got):\n%s", diff)
   280  			}
   281  		})
   282  	}
   283  }