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 }