sigs.k8s.io/kueue@v0.6.2/pkg/workload/admissionchecks_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 workload 18 19 import ( 20 "testing" 21 "time" 22 23 "github.com/google/go-cmp/cmp" 24 "github.com/google/go-cmp/cmp/cmpopts" 25 corev1 "k8s.io/api/core/v1" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/utils/ptr" 28 29 kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1" 30 ) 31 32 func TestSyncAdmittedCondition(t *testing.T) { 33 cases := map[string]struct { 34 checkStates []kueue.AdmissionCheckState 35 conditions []metav1.Condition 36 wantConditions []metav1.Condition 37 wantChange bool 38 }{ 39 "empty": {}, 40 "reservation no checks": { 41 conditions: []metav1.Condition{ 42 { 43 Type: kueue.WorkloadQuotaReserved, 44 Status: metav1.ConditionTrue, 45 }, 46 }, 47 wantConditions: []metav1.Condition{ 48 { 49 Type: kueue.WorkloadQuotaReserved, 50 Status: metav1.ConditionTrue, 51 }, 52 { 53 Type: kueue.WorkloadAdmitted, 54 Status: metav1.ConditionTrue, 55 Reason: "Admitted", 56 }, 57 }, 58 wantChange: true, 59 }, 60 "reservation, checks not ready": { 61 checkStates: []kueue.AdmissionCheckState{ 62 { 63 Name: "check1", 64 State: kueue.CheckStatePending, 65 }, 66 { 67 Name: "check2", 68 State: kueue.CheckStateReady, 69 }, 70 }, 71 conditions: []metav1.Condition{ 72 { 73 Type: kueue.WorkloadQuotaReserved, 74 Status: metav1.ConditionTrue, 75 }, 76 }, 77 wantConditions: []metav1.Condition{ 78 { 79 Type: kueue.WorkloadQuotaReserved, 80 Status: metav1.ConditionTrue, 81 }, 82 }, 83 }, 84 "reservation, checks ready": { 85 checkStates: []kueue.AdmissionCheckState{ 86 { 87 Name: "check1", 88 State: kueue.CheckStateReady, 89 }, 90 { 91 Name: "check2", 92 State: kueue.CheckStateReady, 93 }, 94 }, 95 conditions: []metav1.Condition{ 96 { 97 Type: kueue.WorkloadQuotaReserved, 98 Status: metav1.ConditionTrue, 99 }, 100 }, 101 wantConditions: []metav1.Condition{ 102 { 103 Type: kueue.WorkloadQuotaReserved, 104 Status: metav1.ConditionTrue, 105 }, 106 { 107 Type: kueue.WorkloadAdmitted, 108 Status: metav1.ConditionTrue, 109 Reason: "Admitted", 110 }, 111 }, 112 wantChange: true, 113 }, 114 "reservation lost": { 115 checkStates: []kueue.AdmissionCheckState{ 116 { 117 Name: "check1", 118 State: kueue.CheckStateReady, 119 }, 120 { 121 Name: "check2", 122 State: kueue.CheckStateReady, 123 }, 124 }, 125 conditions: []metav1.Condition{ 126 { 127 Type: kueue.WorkloadAdmitted, 128 Status: metav1.ConditionTrue, 129 }, 130 }, 131 wantConditions: []metav1.Condition{ 132 { 133 Type: kueue.WorkloadAdmitted, 134 Status: metav1.ConditionFalse, 135 Reason: "NoReservation", 136 }, 137 }, 138 wantChange: true, 139 }, 140 "check lost": { 141 checkStates: []kueue.AdmissionCheckState{ 142 { 143 Name: "check1", 144 State: kueue.CheckStateReady, 145 }, 146 { 147 Name: "check2", 148 State: kueue.CheckStatePending, 149 }, 150 }, 151 conditions: []metav1.Condition{ 152 { 153 Type: kueue.WorkloadQuotaReserved, 154 Status: metav1.ConditionTrue, 155 }, 156 { 157 Type: kueue.WorkloadAdmitted, 158 Status: metav1.ConditionTrue, 159 }, 160 }, 161 wantConditions: []metav1.Condition{ 162 { 163 Type: kueue.WorkloadQuotaReserved, 164 Status: metav1.ConditionTrue, 165 }, 166 { 167 Type: kueue.WorkloadAdmitted, 168 Status: metav1.ConditionFalse, 169 Reason: "NoChecks", 170 }, 171 }, 172 wantChange: true, 173 }, 174 "reservation and check lost": { 175 checkStates: []kueue.AdmissionCheckState{ 176 { 177 Name: "check1", 178 State: kueue.CheckStateReady, 179 }, 180 { 181 Name: "check2", 182 State: kueue.CheckStatePending, 183 }, 184 }, 185 conditions: []metav1.Condition{ 186 { 187 Type: kueue.WorkloadAdmitted, 188 Status: metav1.ConditionTrue, 189 }, 190 }, 191 wantConditions: []metav1.Condition{ 192 { 193 Type: kueue.WorkloadAdmitted, 194 Status: metav1.ConditionFalse, 195 Reason: "NoReservationNoChecks", 196 }, 197 }, 198 wantChange: true, 199 }, 200 } 201 202 for name, tc := range cases { 203 t.Run(name, func(t *testing.T) { 204 wl := &kueue.Workload{ 205 Status: kueue.WorkloadStatus{ 206 AdmissionChecks: tc.checkStates, 207 Conditions: tc.conditions, 208 }, 209 } 210 211 gotChange := SyncAdmittedCondition(wl) 212 213 if gotChange != tc.wantChange { 214 t.Errorf("Unexpected change status, expecting %v", tc.wantChange) 215 } 216 217 if diff := cmp.Diff(tc.wantConditions, wl.Status.Conditions, cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime", "Message")); diff != "" { 218 t.Errorf("Unexpected conditions after sync (- want/+ got):\n%s", diff) 219 } 220 }) 221 } 222 } 223 224 func TestSetCheckState(t *testing.T) { 225 t0 := metav1.NewTime(time.Now().Add(-5 * time.Second)) 226 t1 := metav1.NewTime(time.Now()) 227 ps1Updates := kueue.PodSetUpdate{ 228 Name: "ps1", 229 Labels: map[string]string{ 230 "l1": "l1v", 231 }, 232 Annotations: map[string]string{ 233 "a1": "a1v", 234 }, 235 NodeSelector: map[string]string{ 236 "ns1": "ms1v", 237 }, 238 Tolerations: []corev1.Toleration{ 239 { 240 Key: "t1", 241 Operator: corev1.TolerationOpEqual, 242 Value: "t1v", 243 Effect: corev1.TaintEffectNoSchedule, 244 TolerationSeconds: ptr.To[int64](5), 245 }, 246 }, 247 } 248 249 cases := map[string]struct { 250 origStates []kueue.AdmissionCheckState 251 state kueue.AdmissionCheckState 252 wantStates []kueue.AdmissionCheckState 253 }{ 254 "add new check": { 255 origStates: []kueue.AdmissionCheckState{}, 256 state: kueue.AdmissionCheckState{ 257 Name: "check1", 258 State: kueue.CheckStatePending, 259 LastTransitionTime: *t0.DeepCopy(), 260 Message: "msg1", 261 PodSetUpdates: []kueue.PodSetUpdate{*ps1Updates.DeepCopy()}, 262 }, 263 wantStates: []kueue.AdmissionCheckState{ 264 { 265 Name: "check1", 266 State: kueue.CheckStatePending, 267 LastTransitionTime: *t0.DeepCopy(), 268 Message: "msg1", 269 PodSetUpdates: []kueue.PodSetUpdate{*ps1Updates.DeepCopy()}, 270 }, 271 }, 272 }, 273 "update check": { 274 origStates: []kueue.AdmissionCheckState{ 275 { 276 Name: "check1", 277 State: kueue.CheckStatePending, 278 LastTransitionTime: *t0.DeepCopy(), 279 Message: "msg1", 280 PodSetUpdates: nil, 281 }, 282 { 283 Name: "check2", 284 State: kueue.CheckStatePending, 285 LastTransitionTime: *t0.DeepCopy(), 286 Message: "msg1", 287 PodSetUpdates: nil, 288 }, 289 }, 290 state: kueue.AdmissionCheckState{ 291 Name: "check1", 292 State: kueue.CheckStateReady, 293 LastTransitionTime: *t1.DeepCopy(), 294 Message: "msg2", 295 PodSetUpdates: []kueue.PodSetUpdate{*ps1Updates.DeepCopy()}, 296 }, 297 wantStates: []kueue.AdmissionCheckState{ 298 { 299 Name: "check1", 300 State: kueue.CheckStateReady, 301 LastTransitionTime: *t1.DeepCopy(), 302 Message: "msg2", 303 PodSetUpdates: []kueue.PodSetUpdate{*ps1Updates.DeepCopy()}, 304 }, 305 { 306 Name: "check2", 307 State: kueue.CheckStatePending, 308 LastTransitionTime: *t0.DeepCopy(), 309 Message: "msg1", 310 PodSetUpdates: nil, 311 }, 312 }, 313 }, 314 "add new check, no transition tim": { 315 origStates: []kueue.AdmissionCheckState{}, 316 state: kueue.AdmissionCheckState{ 317 Name: "check1", 318 State: kueue.CheckStatePending, 319 Message: "msg1", 320 PodSetUpdates: []kueue.PodSetUpdate{*ps1Updates.DeepCopy()}, 321 }, 322 wantStates: []kueue.AdmissionCheckState{ 323 { 324 Name: "check1", 325 State: kueue.CheckStatePending, 326 Message: "msg1", 327 PodSetUpdates: []kueue.PodSetUpdate{*ps1Updates.DeepCopy()}, 328 }, 329 }, 330 }, 331 "update check, no transition time": { 332 origStates: []kueue.AdmissionCheckState{ 333 { 334 Name: "check1", 335 State: kueue.CheckStatePending, 336 LastTransitionTime: *t0.DeepCopy(), 337 Message: "msg1", 338 PodSetUpdates: nil, 339 }, 340 { 341 Name: "check2", 342 State: kueue.CheckStatePending, 343 LastTransitionTime: *t0.DeepCopy(), 344 Message: "msg1", 345 PodSetUpdates: nil, 346 }, 347 }, 348 state: kueue.AdmissionCheckState{ 349 Name: "check1", 350 State: kueue.CheckStateReady, 351 Message: "msg2", 352 PodSetUpdates: []kueue.PodSetUpdate{*ps1Updates.DeepCopy()}, 353 }, 354 wantStates: []kueue.AdmissionCheckState{ 355 { 356 Name: "check1", 357 State: kueue.CheckStateReady, 358 Message: "msg2", 359 PodSetUpdates: []kueue.PodSetUpdate{*ps1Updates.DeepCopy()}, 360 }, 361 { 362 Name: "check2", 363 State: kueue.CheckStatePending, 364 LastTransitionTime: *t0.DeepCopy(), 365 Message: "msg1", 366 PodSetUpdates: nil, 367 }, 368 }, 369 }, 370 } 371 372 for name, tc := range cases { 373 t.Run(name, func(t *testing.T) { 374 gotStates := tc.origStates 375 376 SetAdmissionCheckState(&gotStates, tc.state) 377 378 opts := []cmp.Option{} 379 if tc.state.LastTransitionTime.IsZero() { 380 opts = append(opts, cmpopts.IgnoreFields(kueue.AdmissionCheckState{}, "LastTransitionTime"), cmpopts.EquateApproxTime(time.Second)) 381 382 if updatedCheck := FindAdmissionCheck(gotStates, tc.state.Name); updatedCheck == nil { 383 t.Error("Cannot find the updated check state") 384 } else { 385 if diff := cmp.Diff(metav1.NewTime(time.Now()), updatedCheck.LastTransitionTime, opts...); diff != "" { 386 t.Errorf("Unexpected LastTransitionTime (- want/+ got):\n%s", diff) 387 } 388 } 389 } 390 391 if diff := cmp.Diff(tc.wantStates, gotStates, opts...); diff != "" { 392 t.Errorf("Unexpected conditions after sync (- want/+ got):\n%s", diff) 393 } 394 }) 395 } 396 }