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  }