sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/crier/reporters/gcs/reporter_test.go (about)

     1  /*
     2  Copyright 2020 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 gcs
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	stdio "io"
    24  	"path"
    25  	"strings"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/GoogleCloudPlatform/testgrid/metadata"
    30  	"github.com/google/go-cmp/cmp"
    31  	"github.com/sirupsen/logrus"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  
    34  	prowv1 "sigs.k8s.io/prow/pkg/apis/prowjobs/v1"
    35  	"sigs.k8s.io/prow/pkg/config"
    36  	"sigs.k8s.io/prow/pkg/io"
    37  	"sigs.k8s.io/prow/pkg/io/fakeopener"
    38  	"sigs.k8s.io/prow/pkg/io/providers"
    39  	"sigs.k8s.io/prow/pkg/pod-utils/clone"
    40  )
    41  
    42  type fca struct {
    43  	c config.Config
    44  }
    45  
    46  func (ca fca) Config() *config.Config {
    47  	return &ca.c
    48  }
    49  
    50  func TestReportJobFinished(t *testing.T) {
    51  	completionTime := &metav1.Time{Time: time.Date(2010, 10, 10, 19, 00, 0, 0, time.UTC)}
    52  	tests := []struct {
    53  		jobState       prowv1.ProwJobState
    54  		completionTime *metav1.Time
    55  		passed         bool
    56  		expectErr      bool
    57  	}{
    58  		{
    59  			jobState:  prowv1.TriggeredState,
    60  			expectErr: true,
    61  		},
    62  		{
    63  			jobState:  prowv1.PendingState,
    64  			expectErr: true,
    65  		},
    66  		{
    67  			jobState:       prowv1.SuccessState,
    68  			completionTime: completionTime,
    69  			passed:         true,
    70  		},
    71  		{
    72  			jobState:       prowv1.AbortedState,
    73  			completionTime: completionTime,
    74  		},
    75  		{
    76  			jobState:       prowv1.ErrorState,
    77  			completionTime: completionTime,
    78  		},
    79  		{
    80  			jobState:       prowv1.FailureState,
    81  			completionTime: completionTime,
    82  		},
    83  	}
    84  	for _, tc := range tests {
    85  		t.Run(fmt.Sprintf("report %s job", tc.jobState), func(t *testing.T) {
    86  			ctx := context.Background()
    87  			cfg := fca{c: config.Config{
    88  				ProwConfig: config.ProwConfig{
    89  					Plank: config.Plank{
    90  						DefaultDecorationConfigs: config.DefaultDecorationMapToSliceTesting(
    91  							map[string]*prowv1.DecorationConfig{"*": {
    92  								GCSConfiguration: &prowv1.GCSConfiguration{
    93  									Bucket:       "kubernetes-jenkins",
    94  									PathPrefix:   "some-prefix",
    95  									PathStrategy: prowv1.PathStrategyLegacy,
    96  									DefaultOrg:   "kubernetes",
    97  									DefaultRepo:  "kubernetes",
    98  								},
    99  							}}),
   100  					},
   101  				},
   102  			}}.Config
   103  			fakeOpener := &fakeopener.FakeOpener{}
   104  			reporter := New(cfg, fakeOpener, false)
   105  
   106  			pj := &prowv1.ProwJob{
   107  				Spec: prowv1.ProwJobSpec{
   108  					Type: prowv1.PresubmitJob,
   109  					Refs: &prowv1.Refs{
   110  						Org:   "kubernetes",
   111  						Repo:  "test-infra",
   112  						Pulls: []prowv1.Pull{{Number: 12345}},
   113  					},
   114  					Agent: prowv1.KubernetesAgent,
   115  					Job:   "my-little-job",
   116  				},
   117  				Status: prowv1.ProwJobStatus{
   118  					State:          tc.jobState,
   119  					StartTime:      metav1.Time{Time: time.Date(2010, 10, 10, 18, 30, 0, 0, time.UTC)},
   120  					CompletionTime: tc.completionTime,
   121  					PodName:        "some-pod",
   122  					BuildID:        "123",
   123  				},
   124  			}
   125  
   126  			err := reporter.reportFinishedJob(ctx, logrus.NewEntry(logrus.StandardLogger()), pj)
   127  			if tc.expectErr {
   128  				if err == nil {
   129  					t.Fatalf("Expected an error, but didn't get one.")
   130  				}
   131  				return
   132  			} else if err != nil {
   133  				t.Fatalf("Unexpected error: %v", err)
   134  			}
   135  
   136  			var content []byte
   137  			for path, buf := range fakeOpener.Buffer {
   138  				if strings.HasSuffix(path, prowv1.FinishedStatusFile) {
   139  					content, err = stdio.ReadAll(buf)
   140  					if err != nil {
   141  						t.Fatalf("Failed reading content: %v", err)
   142  					}
   143  					break
   144  				}
   145  			}
   146  			var result metadata.Finished
   147  			if err := json.Unmarshal(content, &result); err != nil {
   148  				t.Errorf("Couldn't decode result as metadata.Finished: %v", err)
   149  			}
   150  			if result.Timestamp == nil {
   151  				t.Errorf("Expected finished.json timestamp to be %d, but it was nil", pj.Status.CompletionTime.Unix())
   152  			} else if *result.Timestamp != pj.Status.CompletionTime.Unix() {
   153  				t.Errorf("Expected finished.json timestamp to be %d, but got %d", pj.Status.CompletionTime.Unix(), *result.Timestamp)
   154  			}
   155  			if result.Passed == nil {
   156  				t.Errorf("Expected finished.json passed to be %v, but it was nil", tc.passed)
   157  			} else if *result.Passed != tc.passed {
   158  				t.Errorf("Expected finished.json passed to be %v, but got %v", tc.passed, *result.Passed)
   159  			}
   160  		})
   161  	}
   162  }
   163  
   164  func TestReportJobStarted(t *testing.T) {
   165  	tests := []struct {
   166  		name            string
   167  		existingStarted *metadata.Started
   168  		state           prowv1.ProwJobState
   169  		cloneRecord     []clone.Record
   170  		expect          metadata.Started
   171  	}{
   172  		{
   173  			name:  "TriggeredState",
   174  			state: prowv1.TriggeredState,
   175  			expect: metadata.Started{
   176  				Timestamp:             1286735400,
   177  				Pull:                  "12345",
   178  				Repos:                 map[string]string{"kubernetes/test-infra": "main,12345:def456"},
   179  				RepoCommit:            "def456",
   180  				DeprecatedRepoVersion: "def456",
   181  				Metadata:              metadata.Metadata{"uploader": string("crier")},
   182  			},
   183  		},
   184  		{
   185  			name:  "PendingState",
   186  			state: prowv1.PendingState,
   187  			expect: metadata.Started{
   188  				Timestamp:             1286735400,
   189  				Pull:                  "12345",
   190  				Repos:                 map[string]string{"kubernetes/test-infra": "main,12345:def456"},
   191  				RepoCommit:            "def456",
   192  				DeprecatedRepoVersion: "def456",
   193  				Metadata:              metadata.Metadata{"uploader": string("crier")},
   194  			},
   195  		},
   196  		{
   197  			name:  "SuccessState",
   198  			state: prowv1.SuccessState,
   199  			expect: metadata.Started{
   200  				Timestamp:             1286735400,
   201  				Pull:                  "12345",
   202  				Repos:                 map[string]string{"kubernetes/test-infra": "main,12345:def456"},
   203  				RepoCommit:            "def456",
   204  				DeprecatedRepoVersion: "def456",
   205  				Metadata:              metadata.Metadata{"uploader": string("crier")},
   206  			},
   207  		},
   208  		{
   209  			name:  "AbortedState",
   210  			state: prowv1.AbortedState,
   211  			expect: metadata.Started{
   212  				Timestamp:             1286735400,
   213  				Pull:                  "12345",
   214  				Repos:                 map[string]string{"kubernetes/test-infra": "main,12345:def456"},
   215  				RepoCommit:            "def456",
   216  				DeprecatedRepoVersion: "def456",
   217  				Metadata:              metadata.Metadata{"uploader": string("crier")},
   218  			},
   219  		},
   220  		{
   221  			name:  "ErrorState",
   222  			state: prowv1.ErrorState,
   223  			expect: metadata.Started{
   224  				Timestamp:             1286735400,
   225  				Pull:                  "12345",
   226  				Repos:                 map[string]string{"kubernetes/test-infra": "main,12345:def456"},
   227  				RepoCommit:            "def456",
   228  				DeprecatedRepoVersion: "def456",
   229  				Metadata:              metadata.Metadata{"uploader": string("crier")},
   230  			},
   231  		},
   232  		{
   233  			name:  "FailureState",
   234  			state: prowv1.ErrorState,
   235  			expect: metadata.Started{
   236  				Timestamp:             1286735400,
   237  				Pull:                  "12345",
   238  				Repos:                 map[string]string{"kubernetes/test-infra": "main,12345:def456"},
   239  				RepoCommit:            "def456",
   240  				DeprecatedRepoVersion: "def456",
   241  				Metadata:              metadata.Metadata{"uploader": string("crier")},
   242  			},
   243  		},
   244  		{
   245  			name:  "overwrite-crier-uploaded",
   246  			state: prowv1.SuccessState,
   247  			existingStarted: &metadata.Started{
   248  				Timestamp:  1286735400,
   249  				Pull:       "12345",
   250  				Repos:      map[string]string{"kubernetes/test-infra": "main,12345:def456"},
   251  				RepoCommit: "def456",
   252  				Metadata:   metadata.Metadata{"uploader": string("crier")},
   253  			},
   254  			cloneRecord: []clone.Record{
   255  				{
   256  					Refs: prowv1.Refs{
   257  						Org:     "kubernetes",
   258  						Repo:    "test-infra",
   259  						BaseRef: "main",
   260  						Pulls:   []prowv1.Pull{{Number: 12345, SHA: "def456"}},
   261  					},
   262  					FinalSHA: "abc123",
   263  				},
   264  			},
   265  			expect: metadata.Started{
   266  				Timestamp:             1286735400,
   267  				Pull:                  "12345",
   268  				Repos:                 map[string]string{"kubernetes/test-infra": "main,12345:def456"},
   269  				Metadata:              metadata.Metadata{"uploader": string("crier")},
   270  				RepoCommit:            "abc123",
   271  				DeprecatedRepoVersion: "abc123",
   272  			},
   273  		},
   274  		{
   275  			name:  "overwrite-crier-uploaded-without-SHA",
   276  			state: prowv1.SuccessState,
   277  			existingStarted: &metadata.Started{
   278  				Timestamp:  1286735400,
   279  				Pull:       "12345",
   280  				Repos:      map[string]string{"kubernetes/test-infra": "main,12345:def456"},
   281  				RepoCommit: "def456",
   282  				Metadata:   metadata.Metadata{"uploader": string("crier")},
   283  			},
   284  			cloneRecord: []clone.Record{
   285  				{
   286  					Refs: prowv1.Refs{
   287  						Org:     "kubernetes",
   288  						Repo:    "test-infra",
   289  						BaseRef: "main",
   290  						Pulls:   []prowv1.Pull{{Number: 12345, SHA: "def456"}},
   291  					},
   292  					FinalSHA: "abc123",
   293  				},
   294  			},
   295  			expect: metadata.Started{
   296  				Timestamp:             1286735400,
   297  				Pull:                  "12345",
   298  				Repos:                 map[string]string{"kubernetes/test-infra": "main,12345:def456"},
   299  				Metadata:              metadata.Metadata{"uploader": string("crier")},
   300  				RepoCommit:            "abc123",
   301  				DeprecatedRepoVersion: "abc123",
   302  			},
   303  		},
   304  		{
   305  			name:  "no-overwrite-others-uploaded",
   306  			state: prowv1.SuccessState,
   307  			existingStarted: &metadata.Started{
   308  				Timestamp: 1286735400,
   309  				Pull:      "12345",
   310  				Repos:     map[string]string{"kubernetes/test-infra": "main,12345:def456"},
   311  			},
   312  			cloneRecord: []clone.Record{
   313  				{
   314  					Refs: prowv1.Refs{
   315  						Org:     "kubernetes",
   316  						Repo:    "test-infra",
   317  						BaseRef: "main",
   318  						Pulls:   []prowv1.Pull{{Number: 12345}},
   319  					},
   320  					FinalSHA: "abc123",
   321  				},
   322  			},
   323  			expect: metadata.Started{
   324  				Timestamp: 1286735400,
   325  				Pull:      "12345",
   326  				Repos:     map[string]string{"kubernetes/test-infra": "main,12345:def456"},
   327  			},
   328  		},
   329  		{
   330  			name:  "no-cloneref-self-update",
   331  			state: prowv1.SuccessState,
   332  			existingStarted: &metadata.Started{
   333  				Timestamp:  100, // Intentionally wrong timestamp. Crier will change it if it overwrites.
   334  				Pull:       "12345",
   335  				Repos:      map[string]string{"kubernetes/test-infra": "main,12345:def456"},
   336  				RepoCommit: "main",
   337  				Metadata:   metadata.Metadata{"uploader": string("crier")},
   338  			},
   339  			expect: metadata.Started{
   340  				Timestamp:  100,
   341  				Pull:       "12345",
   342  				Repos:      map[string]string{"kubernetes/test-infra": "main,12345:def456"},
   343  				RepoCommit: "main",
   344  				Metadata:   metadata.Metadata{"uploader": string("crier")},
   345  			},
   346  		},
   347  	}
   348  
   349  	for _, tc := range tests {
   350  		tc := tc
   351  		t.Run(tc.name, func(t *testing.T) {
   352  			ctx := context.Background()
   353  			cfg := fca{c: config.Config{
   354  				ProwConfig: config.ProwConfig{
   355  					Plank: config.Plank{
   356  						DefaultDecorationConfigs: config.DefaultDecorationMapToSliceTesting(
   357  							map[string]*prowv1.DecorationConfig{"*": {
   358  								GCSConfiguration: &prowv1.GCSConfiguration{
   359  									Bucket:       "kubernetes-jenkins",
   360  									PathPrefix:   "some-prefix",
   361  									PathStrategy: prowv1.PathStrategyLegacy,
   362  									DefaultOrg:   "kubernetes",
   363  									DefaultRepo:  "kubernetes",
   364  								},
   365  							}}),
   366  					},
   367  				},
   368  			}}.Config
   369  			// Storage path decided by Prow
   370  			const subDir = "some-prefix/pr-logs/pull/test-infra/12345/my-little-job/123"
   371  
   372  			opener := &fakeopener.FakeOpener{}
   373  			if tc.existingStarted != nil {
   374  				content, err := json.Marshal(*tc.existingStarted)
   375  				if err != nil {
   376  					t.Fatalf("Failed to marshal started.json: %v", err)
   377  				}
   378  				storagePath, _ := providers.StoragePath("kubernetes-jenkins", path.Join(subDir, "started.json"))
   379  				if err := io.WriteContent(ctx, logrus.NewEntry(logrus.StandardLogger()), opener, storagePath, content); err != nil {
   380  					t.Fatalf("Failed creating started.json: %v", err)
   381  				}
   382  			}
   383  			if len(tc.cloneRecord) > 0 {
   384  				content, err := json.Marshal(tc.cloneRecord)
   385  				if err != nil {
   386  					t.Fatalf("Failed to marshal clone record: %v", err)
   387  				}
   388  				storagePath, _ := providers.StoragePath("kubernetes-jenkins", path.Join(subDir, "clone-records.json"))
   389  				if err := io.WriteContent(ctx, logrus.NewEntry(logrus.StandardLogger()), opener, storagePath, content); err != nil {
   390  					t.Fatalf("Failed seeding clone-records.json: %v", err)
   391  				}
   392  			}
   393  
   394  			reporter := New(cfg, opener, false)
   395  
   396  			pj := &prowv1.ProwJob{
   397  				Spec: prowv1.ProwJobSpec{
   398  					Type: prowv1.PresubmitJob,
   399  					Refs: &prowv1.Refs{
   400  						Org:     "kubernetes",
   401  						Repo:    "test-infra",
   402  						BaseRef: "main",
   403  						Pulls:   []prowv1.Pull{{Number: 12345, SHA: "def456"}},
   404  					},
   405  					Agent: prowv1.KubernetesAgent,
   406  					Job:   "my-little-job",
   407  				},
   408  				Status: prowv1.ProwJobStatus{
   409  					State:     tc.state,
   410  					StartTime: metav1.Time{Time: time.Date(2010, 10, 10, 18, 30, 0, 0, time.UTC)},
   411  					PodName:   "some-pod",
   412  					BuildID:   "123",
   413  				},
   414  			}
   415  
   416  			err := reporter.reportStartedJob(ctx, logrus.NewEntry(logrus.StandardLogger()), pj)
   417  			if err != nil {
   418  				t.Fatalf("Unexpected error: %v", err)
   419  			}
   420  
   421  			storagePath, _ := providers.StoragePath("kubernetes-jenkins", path.Join(subDir, "started.json"))
   422  			content, err := io.ReadContent(ctx, logrus.WithContext(ctx), opener, storagePath)
   423  			if err != nil {
   424  				t.Fatalf("Failed reading started.json: %v", err)
   425  			}
   426  
   427  			var result metadata.Started
   428  			if err := json.Unmarshal(content, &result); err != nil {
   429  				t.Fatalf("Couldn't decode result as metadata.Started: %v", err)
   430  			}
   431  			if diff := cmp.Diff(tc.expect, result); diff != "" {
   432  				t.Fatalf("Started.json mismatch. Want(-), got(+):\n%s", diff)
   433  			}
   434  		})
   435  	}
   436  }
   437  
   438  func TestReportProwJob(t *testing.T) {
   439  	ctx := context.Background()
   440  	cfg := fca{c: config.Config{
   441  		ProwConfig: config.ProwConfig{
   442  			Plank: config.Plank{
   443  				DefaultDecorationConfigs: config.DefaultDecorationMapToSliceTesting(
   444  					map[string]*prowv1.DecorationConfig{"*": {
   445  						GCSConfiguration: &prowv1.GCSConfiguration{
   446  							Bucket:       "kubernetes-jenkins",
   447  							PathPrefix:   "some-prefix",
   448  							PathStrategy: prowv1.PathStrategyLegacy,
   449  							DefaultOrg:   "kubernetes",
   450  							DefaultRepo:  "kubernetes",
   451  						},
   452  					}}),
   453  			},
   454  		},
   455  	}}.Config
   456  	fakeOpener := &fakeopener.FakeOpener{}
   457  	reporter := New(cfg, fakeOpener, false)
   458  
   459  	pj := &prowv1.ProwJob{
   460  		Spec: prowv1.ProwJobSpec{
   461  			Type: prowv1.PresubmitJob,
   462  			Refs: &prowv1.Refs{
   463  				Org:   "kubernetes",
   464  				Repo:  "test-infra",
   465  				Pulls: []prowv1.Pull{{Number: 12345}},
   466  			},
   467  			Agent: prowv1.KubernetesAgent,
   468  			Job:   "my-little-job",
   469  		},
   470  		Status: prowv1.ProwJobStatus{
   471  			State:          prowv1.SuccessState,
   472  			StartTime:      metav1.Time{Time: time.Date(2010, 10, 10, 18, 30, 0, 0, time.UTC)},
   473  			CompletionTime: &metav1.Time{Time: time.Date(2010, 10, 10, 19, 00, 0, 0, time.UTC)},
   474  			PodName:        "some-pod",
   475  			BuildID:        "123",
   476  		},
   477  	}
   478  
   479  	if err := reporter.reportProwjob(ctx, logrus.NewEntry(logrus.StandardLogger()), pj); err != nil {
   480  		t.Fatalf("Unexpected error calling reportProwjob: %v", err)
   481  	}
   482  
   483  	var content []byte
   484  	var err error
   485  	for p, b := range fakeOpener.Buffer {
   486  		if strings.HasSuffix(p, prowv1.ProwJobFile) {
   487  			content, err = stdio.ReadAll(b)
   488  			if err != nil {
   489  				t.Fatalf("Failed reading content: %v", err)
   490  			}
   491  			break
   492  		}
   493  	}
   494  
   495  	var result prowv1.ProwJob
   496  	if err := json.Unmarshal(content, &result); err != nil {
   497  		t.Fatalf("Couldn't unmarshal %s: %v", prowv1.ProwJobFile, err)
   498  	}
   499  	if !cmp.Equal(*pj, result) {
   500  		t.Fatalf("Input prowjob mismatches output prowjob:\n%s", cmp.Diff(*pj, result))
   501  	}
   502  }
   503  
   504  func TestShouldReport(t *testing.T) {
   505  	tests := []struct {
   506  		name         string
   507  		buildID      string
   508  		shouldReport bool
   509  	}{
   510  		{
   511  			name:         "tests with a build ID should be reported",
   512  			buildID:      "123",
   513  			shouldReport: true,
   514  		},
   515  		{
   516  			name:         "tests without a build ID should not be reported",
   517  			buildID:      "",
   518  			shouldReport: false,
   519  		},
   520  	}
   521  	for _, tc := range tests {
   522  		t.Run(tc.name, func(t *testing.T) {
   523  			pj := &prowv1.ProwJob{
   524  				Spec: prowv1.ProwJobSpec{
   525  					Type:  prowv1.PostsubmitJob,
   526  					Agent: prowv1.KubernetesAgent,
   527  					Job:   "my-little-job",
   528  				},
   529  				Status: prowv1.ProwJobStatus{
   530  					State:     prowv1.TriggeredState,
   531  					StartTime: metav1.Time{Time: time.Date(2010, 10, 10, 18, 30, 0, 0, time.UTC)},
   532  					BuildID:   tc.buildID,
   533  				},
   534  			}
   535  			gr := New(fca{}.Config, nil, false)
   536  			result := gr.ShouldReport(context.Background(), logrus.NewEntry(logrus.StandardLogger()), pj)
   537  			if result != tc.shouldReport {
   538  				t.Errorf("Got ShouldReport() returned %v, but expected %v", result, tc.shouldReport)
   539  			}
   540  		})
   541  	}
   542  }