github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/pjutil/abort_test.go (about) 1 /* 2 Copyright 2019 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 pjutil 18 19 import ( 20 "context" 21 "reflect" 22 "testing" 23 "time" 24 25 "github.com/sirupsen/logrus" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/util/sets" 28 fakectrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" 29 30 prowv1 "sigs.k8s.io/prow/pkg/apis/prowjobs/v1" 31 ) 32 33 func TestTerminateOlderJobs(t *testing.T) { 34 fakePJNS := "prow-job" 35 now := time.Now() 36 nowFn := func() *metav1.Time { 37 reallyNow := metav1.NewTime(now) 38 return &reallyNow 39 } 40 cases := []struct { 41 name string 42 pjs []prowv1.ProwJob 43 expectedAbortedPJs sets.Set[string] 44 }{ 45 { 46 name: "terminate all older presubmit jobs", 47 pjs: []prowv1.ProwJob{ 48 { 49 ObjectMeta: metav1.ObjectMeta{Name: "newest", Namespace: fakePJNS}, 50 Spec: prowv1.ProwJobSpec{ 51 Type: prowv1.PresubmitJob, 52 Job: "j1", 53 Refs: &prowv1.Refs{ 54 Repo: "test", 55 Pulls: []prowv1.Pull{{Number: 1}}, 56 }, 57 }, 58 Status: prowv1.ProwJobStatus{ 59 StartTime: metav1.NewTime(now.Add(-time.Minute)), 60 }, 61 }, 62 { 63 ObjectMeta: metav1.ObjectMeta{Name: "old", Namespace: fakePJNS}, 64 Spec: prowv1.ProwJobSpec{ 65 Type: prowv1.PresubmitJob, 66 Job: "j1", 67 Refs: &prowv1.Refs{ 68 Repo: "test", 69 Pulls: []prowv1.Pull{{Number: 1}}, 70 }, 71 }, 72 Status: prowv1.ProwJobStatus{ 73 StartTime: metav1.NewTime(now.Add(-time.Hour)), 74 }, 75 }, 76 { 77 ObjectMeta: metav1.ObjectMeta{Name: "older", Namespace: fakePJNS}, 78 Spec: prowv1.ProwJobSpec{ 79 Type: prowv1.PresubmitJob, 80 Job: "j1", 81 Refs: &prowv1.Refs{ 82 Repo: "test", 83 Pulls: []prowv1.Pull{{Number: 1}}, 84 }, 85 }, 86 Status: prowv1.ProwJobStatus{ 87 StartTime: metav1.NewTime(now.Add(-2 * time.Hour)), 88 }, 89 }, 90 { 91 ObjectMeta: metav1.ObjectMeta{Name: "postsubmit", Namespace: fakePJNS}, 92 Spec: prowv1.ProwJobSpec{ 93 Type: prowv1.PostsubmitJob, 94 Job: "j1", 95 Refs: &prowv1.Refs{ 96 Repo: "test", 97 Pulls: []prowv1.Pull{{Number: 1}}, 98 }, 99 }, 100 Status: prowv1.ProwJobStatus{ 101 StartTime: metav1.NewTime(now.Add(-2 * time.Hour)), 102 }, 103 }, 104 { 105 ObjectMeta: metav1.ObjectMeta{Name: "completed", Namespace: fakePJNS}, 106 Spec: prowv1.ProwJobSpec{ 107 Type: prowv1.PresubmitJob, 108 Job: "j1", 109 Refs: &prowv1.Refs{ 110 Repo: "test", 111 Pulls: []prowv1.Pull{{Number: 1}}, 112 }, 113 }, 114 Status: prowv1.ProwJobStatus{ 115 StartTime: metav1.NewTime(now.Add(-2 * time.Hour)), 116 CompletionTime: nowFn(), 117 }, 118 }, 119 }, 120 expectedAbortedPJs: sets.New[string]("old", "older"), 121 }, 122 { 123 name: "Don't terminate older batch jobs", 124 pjs: []prowv1.ProwJob{ 125 { 126 ObjectMeta: metav1.ObjectMeta{Name: "newest", Namespace: fakePJNS}, 127 Spec: prowv1.ProwJobSpec{ 128 Type: prowv1.BatchJob, 129 Job: "j1", 130 Refs: &prowv1.Refs{ 131 Repo: "test", 132 Pulls: []prowv1.Pull{{Number: 1}}, 133 }, 134 }, 135 Status: prowv1.ProwJobStatus{ 136 StartTime: metav1.NewTime(now.Add(-time.Minute)), 137 }, 138 }, 139 { 140 ObjectMeta: metav1.ObjectMeta{Name: "old", Namespace: fakePJNS}, 141 Spec: prowv1.ProwJobSpec{ 142 Type: prowv1.BatchJob, 143 Job: "j1", 144 Refs: &prowv1.Refs{ 145 Repo: "test", 146 Pulls: []prowv1.Pull{{Number: 1}}, 147 }, 148 }, 149 Status: prowv1.ProwJobStatus{ 150 StartTime: metav1.NewTime(now.Add(-time.Hour)), 151 }, 152 }, 153 { 154 ObjectMeta: metav1.ObjectMeta{Name: "older", Namespace: fakePJNS}, 155 Spec: prowv1.ProwJobSpec{ 156 Type: prowv1.BatchJob, 157 Job: "j1", 158 Refs: &prowv1.Refs{ 159 Repo: "test", 160 Pulls: []prowv1.Pull{{Number: 1}}, 161 }, 162 }, 163 Status: prowv1.ProwJobStatus{ 164 StartTime: metav1.NewTime(now.Add(-2 * time.Hour)), 165 }, 166 }, 167 { 168 ObjectMeta: metav1.ObjectMeta{Name: "postsubmit", Namespace: fakePJNS}, 169 Spec: prowv1.ProwJobSpec{ 170 Type: prowv1.PostsubmitJob, 171 Job: "j1", 172 Refs: &prowv1.Refs{ 173 Repo: "test", 174 Pulls: []prowv1.Pull{{Number: 1}}, 175 }, 176 }, 177 Status: prowv1.ProwJobStatus{ 178 StartTime: metav1.NewTime(now.Add(-2 * time.Hour)), 179 }, 180 }, 181 { 182 ObjectMeta: metav1.ObjectMeta{Name: "completed", Namespace: fakePJNS}, 183 Spec: prowv1.ProwJobSpec{ 184 Type: prowv1.BatchJob, 185 Job: "j1", 186 Refs: &prowv1.Refs{ 187 Repo: "test", 188 Pulls: []prowv1.Pull{{Number: 1}}, 189 }, 190 }, 191 Status: prowv1.ProwJobStatus{ 192 StartTime: metav1.NewTime(now.Add(-2 * time.Hour)), 193 CompletionTime: nowFn(), 194 }, 195 }, 196 }, 197 expectedAbortedPJs: sets.New[string](), 198 }, 199 { 200 name: "terminate older jobs with different orders of refs", 201 pjs: []prowv1.ProwJob{ 202 { 203 ObjectMeta: metav1.ObjectMeta{Name: "newest", Namespace: fakePJNS}, 204 Spec: prowv1.ProwJobSpec{ 205 Type: prowv1.PresubmitJob, 206 Job: "j1", 207 Refs: &prowv1.Refs{ 208 Repo: "test", 209 Pulls: []prowv1.Pull{{Number: 1}, {Number: 2}}, 210 }, 211 }, 212 Status: prowv1.ProwJobStatus{ 213 StartTime: metav1.NewTime(now.Add(-time.Minute)), 214 }, 215 }, 216 { 217 ObjectMeta: metav1.ObjectMeta{Name: "old", Namespace: fakePJNS}, 218 Spec: prowv1.ProwJobSpec{ 219 Type: prowv1.PresubmitJob, 220 Job: "j1", 221 Refs: &prowv1.Refs{ 222 Repo: "test", 223 Pulls: []prowv1.Pull{{Number: 2}, {Number: 1}}, 224 }, 225 }, 226 Status: prowv1.ProwJobStatus{ 227 StartTime: metav1.NewTime(now.Add(-time.Minute)), 228 }, 229 }, 230 }, 231 expectedAbortedPJs: sets.New[string]("old"), 232 }, 233 { 234 name: "terminate older jobs with different orders of extra refs", 235 pjs: []prowv1.ProwJob{ 236 { 237 ObjectMeta: metav1.ObjectMeta{Name: "newest", Namespace: fakePJNS}, 238 Spec: prowv1.ProwJobSpec{ 239 Type: prowv1.PresubmitJob, 240 Job: "j1", 241 Refs: &prowv1.Refs{ 242 Repo: "test", 243 Pulls: []prowv1.Pull{{Number: 1}}, 244 }, 245 ExtraRefs: []prowv1.Refs{ 246 { 247 Repo: "other", 248 Pulls: []prowv1.Pull{{Number: 2}}, 249 }, 250 { 251 Repo: "something", 252 Pulls: []prowv1.Pull{{Number: 3}}, 253 }, 254 }, 255 }, 256 Status: prowv1.ProwJobStatus{ 257 StartTime: metav1.NewTime(now.Add(-time.Minute)), 258 }, 259 }, 260 { 261 ObjectMeta: metav1.ObjectMeta{Name: "old", Namespace: fakePJNS}, 262 Spec: prowv1.ProwJobSpec{ 263 Type: prowv1.PresubmitJob, 264 Job: "j1", 265 Refs: &prowv1.Refs{ 266 Repo: "test", 267 Pulls: []prowv1.Pull{{Number: 1}}, 268 }, 269 ExtraRefs: []prowv1.Refs{ 270 { 271 Repo: "something", 272 Pulls: []prowv1.Pull{{Number: 3}}, 273 }, 274 { 275 Repo: "other", 276 Pulls: []prowv1.Pull{{Number: 2}}, 277 }, 278 }, 279 }, 280 Status: prowv1.ProwJobStatus{ 281 StartTime: metav1.NewTime(now.Add(-time.Minute)), 282 }, 283 }, 284 }, 285 expectedAbortedPJs: sets.New[string]("old"), 286 }, 287 { 288 name: "terminate older jobs with no main refs, only extra refs", 289 pjs: []prowv1.ProwJob{ 290 { 291 ObjectMeta: metav1.ObjectMeta{Name: "newest", Namespace: fakePJNS}, 292 Spec: prowv1.ProwJobSpec{ 293 Type: prowv1.PresubmitJob, 294 Job: "j1", 295 ExtraRefs: []prowv1.Refs{ 296 { 297 Repo: "test", 298 Pulls: []prowv1.Pull{{Number: 1}}, 299 }, 300 }, 301 }, 302 Status: prowv1.ProwJobStatus{ 303 StartTime: metav1.NewTime(now.Add(-time.Minute)), 304 }, 305 }, 306 { 307 ObjectMeta: metav1.ObjectMeta{Name: "old", Namespace: fakePJNS}, 308 Spec: prowv1.ProwJobSpec{ 309 Type: prowv1.PresubmitJob, 310 Job: "j1", 311 ExtraRefs: []prowv1.Refs{ 312 { 313 Repo: "test", 314 Pulls: []prowv1.Pull{{Number: 1}}, 315 }, 316 }, 317 }, 318 Status: prowv1.ProwJobStatus{ 319 StartTime: metav1.NewTime(now.Add(-time.Minute)), 320 }, 321 }, 322 }, 323 expectedAbortedPJs: sets.New[string]("old"), 324 }, 325 { 326 name: "terminate older jobs with different base SHA", 327 pjs: []prowv1.ProwJob{ 328 { 329 ObjectMeta: metav1.ObjectMeta{Name: "newest", Namespace: fakePJNS}, 330 Spec: prowv1.ProwJobSpec{ 331 Type: prowv1.PresubmitJob, 332 Job: "j1", 333 Refs: &prowv1.Refs{ 334 Repo: "test", 335 BaseSHA: "foo", 336 Pulls: []prowv1.Pull{{Number: 1}}, 337 }, 338 }, 339 Status: prowv1.ProwJobStatus{ 340 StartTime: metav1.NewTime(now.Add(-time.Minute)), 341 }, 342 }, 343 { 344 ObjectMeta: metav1.ObjectMeta{Name: "old", Namespace: fakePJNS}, 345 Spec: prowv1.ProwJobSpec{ 346 Type: prowv1.PresubmitJob, 347 Job: "j1", 348 Refs: &prowv1.Refs{ 349 Repo: "test", 350 BaseSHA: "bar", 351 Pulls: []prowv1.Pull{{Number: 1}}, 352 }, 353 }, 354 Status: prowv1.ProwJobStatus{ 355 StartTime: metav1.NewTime(now.Add(-time.Minute)), 356 }, 357 }, 358 }, 359 expectedAbortedPJs: sets.New[string]("old"), 360 }, 361 { 362 name: "don't terminate older jobs with different base refs", 363 pjs: []prowv1.ProwJob{ 364 { 365 ObjectMeta: metav1.ObjectMeta{Name: "newest", Namespace: fakePJNS}, 366 Spec: prowv1.ProwJobSpec{ 367 Type: prowv1.PresubmitJob, 368 Job: "j1", 369 Refs: &prowv1.Refs{ 370 Repo: "test", 371 BaseRef: "foo", 372 Pulls: []prowv1.Pull{{Number: 1}}, 373 }, 374 }, 375 Status: prowv1.ProwJobStatus{ 376 StartTime: metav1.NewTime(now.Add(-time.Minute)), 377 }, 378 }, 379 { 380 ObjectMeta: metav1.ObjectMeta{Name: "old", Namespace: fakePJNS}, 381 Spec: prowv1.ProwJobSpec{ 382 Type: prowv1.PresubmitJob, 383 Job: "j1", 384 Refs: &prowv1.Refs{ 385 Repo: "test", 386 BaseRef: "bar", 387 Pulls: []prowv1.Pull{{Number: 1}}, 388 }, 389 }, 390 Status: prowv1.ProwJobStatus{ 391 StartTime: metav1.NewTime(now.Add(-time.Minute)), 392 }, 393 }, 394 }, 395 expectedAbortedPJs: sets.New[string](), 396 }, 397 { 398 name: "terminate older jobs with different pull sha", 399 pjs: []prowv1.ProwJob{ 400 { 401 ObjectMeta: metav1.ObjectMeta{Name: "newest", Namespace: fakePJNS}, 402 Spec: prowv1.ProwJobSpec{ 403 Type: prowv1.PresubmitJob, 404 Job: "j1", 405 Refs: &prowv1.Refs{ 406 Repo: "test", 407 Pulls: []prowv1.Pull{{Number: 1, SHA: "foo"}}, 408 }, 409 }, 410 Status: prowv1.ProwJobStatus{ 411 StartTime: metav1.NewTime(now.Add(-time.Minute)), 412 }, 413 }, 414 { 415 ObjectMeta: metav1.ObjectMeta{Name: "old", Namespace: fakePJNS}, 416 Spec: prowv1.ProwJobSpec{ 417 Type: prowv1.PresubmitJob, 418 Job: "j1", 419 Refs: &prowv1.Refs{ 420 Repo: "test", 421 Pulls: []prowv1.Pull{{Number: 1, SHA: "bar"}}, 422 }, 423 }, 424 Status: prowv1.ProwJobStatus{ 425 StartTime: metav1.NewTime(now.Add(-time.Minute)), 426 }, 427 }, 428 }, 429 expectedAbortedPJs: sets.New[string]("old"), 430 }, 431 } 432 433 for _, tc := range cases { 434 t.Run(tc.name, func(t *testing.T) { 435 builder := fakectrlruntimeclient.NewClientBuilder() 436 var origPJs []prowv1.ProwJob 437 for i := range tc.pjs { 438 builder.WithRuntimeObjects(&tc.pjs[i]) 439 origPJs = append(origPJs, tc.pjs[i]) 440 } 441 fakeProwJobClient := builder.Build() 442 log := logrus.NewEntry(logrus.StandardLogger()) 443 if err := TerminateOlderJobs(fakeProwJobClient, log, tc.pjs); err != nil { 444 t.Fatalf("%s: error terminating the older presubmit jobs: %v", tc.name, err) 445 } 446 447 var actualPJs prowv1.ProwJobList 448 if err := fakeProwJobClient.List(context.Background(), &actualPJs); err != nil { 449 t.Fatalf("failed to list prowjobs: %v", err) 450 } 451 452 actuallyAbortedJobs := sets.Set[string]{} 453 for _, job := range actualPJs.Items { 454 if job.Status.State == prowv1.AbortedState { 455 if job.Complete() { 456 t.Errorf("job %s was set to complete, TerminateOlderJobs must never set prowjobs as completed", job.Name) 457 } 458 actuallyAbortedJobs.Insert(job.Name) 459 } 460 } 461 462 if missing := tc.expectedAbortedPJs.Difference(actuallyAbortedJobs); missing.Len() > 0 { 463 t.Errorf("%s: did not replace the expected jobs: %v", tc.name, missing.Len()) 464 } 465 if extra := actuallyAbortedJobs.Difference(tc.expectedAbortedPJs); extra.Len() > 0 { 466 t.Errorf("%s: found unexpectedly replaced job: %v", tc.name, sets.List(extra)) 467 } 468 469 // Validate that terminated PJs are marked terminated in the passed slice. 470 // Only consider jobs that we expected to be replaced and that were replaced. 471 replacedAsExpected := actuallyAbortedJobs.Intersection(tc.expectedAbortedPJs) 472 for i := range origPJs { 473 if replacedAsExpected.Has(origPJs[i].Name) { 474 if reflect.DeepEqual(origPJs[i], tc.pjs[i]) { 475 t.Errorf("%s: job %q was terminated, but not updated in the slice", tc.name, origPJs[i].Name) 476 } 477 } 478 } 479 }) 480 } 481 }