sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/spyglass/podlogartifact_test.go (about)

     1  /*
     2  Copyright 2018 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 spyglass
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"io"
    23  	"testing"
    24  
    25  	prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1"
    26  	"sigs.k8s.io/prow/pkg/kube"
    27  	"sigs.k8s.io/prow/pkg/spyglass/api"
    28  	"sigs.k8s.io/prow/pkg/spyglass/lenses"
    29  )
    30  
    31  const customContainerName = "custom-container"
    32  
    33  // fakePodLogJAgent used for pod log artifact dependency injection
    34  type fakePodLogJAgent struct {
    35  }
    36  
    37  func (j *fakePodLogJAgent) GetProwJob(job, id string) (prowapi.ProwJob, error) {
    38  	return prowapi.ProwJob{}, nil
    39  }
    40  
    41  func (j *fakePodLogJAgent) GetJobLog(job, id, container string) ([]byte, error) {
    42  	if job == "BFG" && id == "435" {
    43  		switch container {
    44  		case kube.TestContainerName:
    45  			return []byte("frobscottle"), nil
    46  		case customContainerName:
    47  			return []byte("snozzcumber"), nil
    48  		}
    49  	} else if job == "Fantastic Mr. Fox" && id == "4" {
    50  		return []byte("a hundred smoked hams and fifty sides of bacon"), nil
    51  	}
    52  	return nil, fmt.Errorf("could not find job %s, id %s, container %s", job, id, container)
    53  }
    54  
    55  func TestNewPodLogArtifact(t *testing.T) {
    56  	testCases := []struct {
    57  		name         string
    58  		jobName      string
    59  		buildID      string
    60  		container    string
    61  		artifact     string
    62  		sizeLimit    int64
    63  		expectedErr  error
    64  		expectedLink string
    65  	}{
    66  		{
    67  			name:         "Create pod log with valid fields",
    68  			jobName:      "job",
    69  			buildID:      "123",
    70  			container:    kube.TestContainerName,
    71  			artifact:     singleLogName,
    72  			sizeLimit:    500e6,
    73  			expectedErr:  nil,
    74  			expectedLink: fmt.Sprintf("/log?container=%s&id=123&job=job", kube.TestContainerName),
    75  		},
    76  		{
    77  			name:         "Create pod log with valid fields and custom container",
    78  			jobName:      "job",
    79  			buildID:      "123",
    80  			container:    customContainerName,
    81  			artifact:     fmt.Sprintf("%s-%s", customContainerName, singleLogName),
    82  			sizeLimit:    500e6,
    83  			expectedErr:  nil,
    84  			expectedLink: fmt.Sprintf("/log?container=%s&id=123&job=job", customContainerName),
    85  		},
    86  		{
    87  			name:         "Create pod log with no jobName",
    88  			jobName:      "",
    89  			buildID:      "123",
    90  			container:    kube.TestContainerName,
    91  			artifact:     singleLogName,
    92  			sizeLimit:    500e6,
    93  			expectedErr:  errInsufficientJobInfo,
    94  			expectedLink: "",
    95  		},
    96  		{
    97  			name:         "Create pod log with no buildID",
    98  			jobName:      "job",
    99  			buildID:      "",
   100  			container:    kube.TestContainerName,
   101  			artifact:     singleLogName,
   102  			sizeLimit:    500e6,
   103  			expectedErr:  errInsufficientJobInfo,
   104  			expectedLink: "",
   105  		},
   106  		{
   107  			name:         "Create pod log with negative sizeLimit",
   108  			jobName:      "job",
   109  			buildID:      "123",
   110  			container:    kube.TestContainerName,
   111  			artifact:     singleLogName,
   112  			sizeLimit:    -4,
   113  			expectedErr:  errInvalidSizeLimit,
   114  			expectedLink: "",
   115  		},
   116  	}
   117  	for _, tc := range testCases {
   118  		t.Run(tc.name, func(t *testing.T) {
   119  			artifact, err := NewPodLogArtifact(tc.jobName, tc.buildID, tc.artifact, tc.container, tc.sizeLimit, &fakePodLogJAgent{})
   120  			if err != nil {
   121  				if err != tc.expectedErr {
   122  					t.Fatalf("failed creating artifact. err: %v", err)
   123  				}
   124  				return
   125  			}
   126  			link := artifact.CanonicalLink()
   127  			if link != tc.expectedLink {
   128  				t.Errorf("Unexpected link, expected %s, got %q", tc.expectedLink, link)
   129  			}
   130  		})
   131  	}
   132  }
   133  
   134  func TestReadTail_PodLog(t *testing.T) {
   135  	testCases := []struct {
   136  		name      string
   137  		jobName   string
   138  		buildID   string
   139  		container string
   140  		artifact  *PodLogArtifact
   141  		n         int64
   142  		expected  []byte
   143  		expectErr bool
   144  	}{
   145  		{
   146  			name:      "Podlog ReadTail longer than contents",
   147  			jobName:   "BFG",
   148  			buildID:   "435",
   149  			container: kube.TestContainerName,
   150  			n:         50,
   151  			expected:  []byte("frobscottle"),
   152  		},
   153  		{
   154  			name:      "Podlog ReadTail shorter than contents",
   155  			jobName:   "Fantastic Mr. Fox",
   156  			buildID:   "4",
   157  			container: kube.TestContainerName,
   158  			n:         3,
   159  			expected:  []byte("con"),
   160  		},
   161  		{
   162  			name:      "Podlog ReadTail longer for different container",
   163  			jobName:   "BFG",
   164  			buildID:   "435",
   165  			container: customContainerName,
   166  			n:         50,
   167  			expected:  []byte("snozzcumber"),
   168  		},
   169  		{
   170  			name:      "Podlog ReadTail shorter for different container",
   171  			jobName:   "BFG",
   172  			buildID:   "435",
   173  			container: customContainerName,
   174  			n:         3,
   175  			expected:  []byte("ber"),
   176  		},
   177  		{
   178  			name:      "Podlog ReadTail nonexistent pod",
   179  			jobName:   "Fax",
   180  			buildID:   "4",
   181  			container: kube.TestContainerName,
   182  			n:         3,
   183  			expectErr: true,
   184  		},
   185  	}
   186  	for _, tc := range testCases {
   187  		t.Run(tc.name, func(t *testing.T) {
   188  			artifact, err := NewPodLogArtifact(tc.jobName, tc.buildID, singleLogName, tc.container, 500e6, &fakePodLogJAgent{})
   189  			if err != nil {
   190  				t.Fatalf("Pod Log Tests failed to create pod log artifact, err %v", err)
   191  			}
   192  			res, err := artifact.ReadTail(tc.n)
   193  			if err != nil && !tc.expectErr {
   194  				t.Fatalf("failed reading bytes of log. did not expect err, got err: %v", err)
   195  			}
   196  			if err == nil && tc.expectErr {
   197  				t.Errorf("expected an error, got none")
   198  			}
   199  			if !bytes.Equal(tc.expected, res) {
   200  				t.Errorf("Unexpected result of reading pod logs, expected %q, got %q", tc.expected, res)
   201  			}
   202  		})
   203  	}
   204  
   205  }
   206  func TestReadAt_PodLog(t *testing.T) {
   207  	testCases := []struct {
   208  		name        string
   209  		jobName     string
   210  		buildID     string
   211  		container   string
   212  		n           int64
   213  		offset      int64
   214  		expectedErr error
   215  		expected    []byte
   216  	}{
   217  		{
   218  			name:        "Podlog ReadAt range longer than contents",
   219  			n:           100,
   220  			jobName:     "BFG",
   221  			buildID:     "435",
   222  			container:   kube.TestContainerName,
   223  			offset:      3,
   224  			expectedErr: io.EOF,
   225  			expected:    []byte("bscottle"),
   226  		},
   227  		{
   228  			name:        "Podlog ReadAt range within contents",
   229  			n:           4,
   230  			jobName:     "Fantastic Mr. Fox",
   231  			buildID:     "4",
   232  			container:   kube.TestContainerName,
   233  			offset:      2,
   234  			expectedErr: nil,
   235  			expected:    []byte("hund"),
   236  		},
   237  	}
   238  	for _, tc := range testCases {
   239  		t.Run(tc.name, func(t *testing.T) {
   240  			artifact, err := NewPodLogArtifact(tc.jobName, tc.buildID, singleLogName, tc.container, 500e6, &fakePodLogJAgent{})
   241  			if err != nil {
   242  				t.Fatalf("Pod Log Tests failed to create pod log artifact, err %v", err)
   243  			}
   244  			res := make([]byte, tc.n)
   245  			readBytes, err := artifact.ReadAt(res, tc.offset)
   246  			if err != tc.expectedErr {
   247  				t.Fatalf("failed reading bytes of log. err: %v, expected err: %v", err, tc.expectedErr)
   248  			}
   249  			if !bytes.Equal(tc.expected, res[:readBytes]) {
   250  				t.Errorf("Unexpected result of reading pod logs, expected %q, got %q", tc.expected, res)
   251  			}
   252  		})
   253  	}
   254  
   255  }
   256  func TestReadAtMost_PodLog(t *testing.T) {
   257  	testCases := []struct {
   258  		name        string
   259  		n           int64
   260  		jobName     string
   261  		buildID     string
   262  		container   string
   263  		expectedErr error
   264  		expected    []byte
   265  	}{
   266  		{
   267  			name:        "Podlog ReadAtMost longer than contents",
   268  			jobName:     "BFG",
   269  			buildID:     "435",
   270  			container:   kube.TestContainerName,
   271  			n:           100,
   272  			expectedErr: io.EOF,
   273  			expected:    []byte("frobscottle"),
   274  		},
   275  		{
   276  			name:        "Podlog ReadAtMost shorter than contents",
   277  			n:           3,
   278  			jobName:     "BFG",
   279  			buildID:     "435",
   280  			container:   kube.TestContainerName,
   281  			expectedErr: nil,
   282  			expected:    []byte("fro"),
   283  		},
   284  	}
   285  	for _, tc := range testCases {
   286  		t.Run(tc.name, func(t *testing.T) {
   287  			artifact, err := NewPodLogArtifact(tc.jobName, tc.buildID, singleLogName, tc.container, 500e6, &fakePodLogJAgent{})
   288  			if err != nil {
   289  				t.Fatalf("Pod Log Tests failed to create pod log artifact, err %v", err)
   290  			}
   291  			res, err := artifact.ReadAtMost(tc.n)
   292  			if err != tc.expectedErr {
   293  				t.Fatalf("failed reading bytes of log. err: %v, expected err: %v", err, tc.expectedErr)
   294  			}
   295  			if !bytes.Equal(tc.expected, res) {
   296  				t.Errorf("Unexpected result of reading pod logs, expected %q, got %q", tc.expected, res)
   297  			}
   298  		})
   299  	}
   300  
   301  }
   302  
   303  func TestReadAll_PodLog(t *testing.T) {
   304  	fakePodLogAgent := &fakePodLogJAgent{}
   305  	testCases := []struct {
   306  		name        string
   307  		jobName     string
   308  		buildID     string
   309  		container   string
   310  		sizeLimit   int64
   311  		expectedErr error
   312  		expected    []byte
   313  	}{
   314  		{
   315  			name:        "Podlog readall not found",
   316  			jobName:     "job",
   317  			buildID:     "123",
   318  			container:   kube.TestContainerName,
   319  			sizeLimit:   500e6,
   320  			expectedErr: fmt.Errorf("error getting pod log size: error getting size of pod log: could not find job job, id 123, container %s", kube.TestContainerName),
   321  			expected:    nil,
   322  		},
   323  		{
   324  			name:        "Simple \"BFG\" Podlog readall",
   325  			jobName:     "BFG",
   326  			buildID:     "435",
   327  			container:   kube.TestContainerName,
   328  			sizeLimit:   500e6,
   329  			expectedErr: nil,
   330  			expected:    []byte("frobscottle"),
   331  		},
   332  		{
   333  			name:        "Simple \"BFG\" Podlog readall for custom container",
   334  			jobName:     "BFG",
   335  			buildID:     "435",
   336  			container:   customContainerName,
   337  			sizeLimit:   500e6,
   338  			expectedErr: nil,
   339  			expected:    []byte("snozzcumber"),
   340  		},
   341  		{
   342  			name:        "\"Fantastic Mr. Fox\" Podlog readall",
   343  			jobName:     "Fantastic Mr. Fox",
   344  			buildID:     "4",
   345  			container:   kube.TestContainerName,
   346  			sizeLimit:   500e6,
   347  			expectedErr: nil,
   348  			expected:    []byte("a hundred smoked hams and fifty sides of bacon"),
   349  		},
   350  		{
   351  			name:        "Podlog readall over size limit",
   352  			jobName:     "Fantastic Mr. Fox",
   353  			buildID:     "4",
   354  			container:   kube.TestContainerName,
   355  			sizeLimit:   5,
   356  			expectedErr: lenses.ErrFileTooLarge,
   357  			expected:    nil,
   358  		},
   359  	}
   360  	for _, tc := range testCases {
   361  		artifact, err := NewPodLogArtifact(tc.jobName, tc.buildID, singleLogName, tc.container, tc.sizeLimit, fakePodLogAgent)
   362  		if err != nil {
   363  			t.Fatalf("Pod Log Tests failed to create pod log artifact, err %v", err)
   364  		}
   365  		res, err := artifact.ReadAll()
   366  		if err != nil && err.Error() != tc.expectedErr.Error() {
   367  			t.Fatalf("%s failed reading bytes of log. got err: %v, expected err: %v", tc.name, err, tc.expectedErr)
   368  		}
   369  		if err != nil {
   370  			continue
   371  		}
   372  		if !bytes.Equal(tc.expected, res) {
   373  			t.Errorf("Unexpected result of reading pod logs, expected %q, got %q", tc.expected, res)
   374  		}
   375  
   376  	}
   377  
   378  }
   379  
   380  type fakeAgent struct {
   381  	contents string
   382  }
   383  
   384  func (f *fakeAgent) GetProwJob(job, id string) (prowapi.ProwJob, error) {
   385  	return prowapi.ProwJob{}, nil
   386  }
   387  
   388  func (f *fakeAgent) GetJobLog(job, id, container string) ([]byte, error) {
   389  	return []byte(f.contents), nil
   390  }
   391  
   392  func TestPodLogArtifact_RespectsSizeLimit(t *testing.T) {
   393  	contents := "Supercalifragilisticexpialidocious"
   394  	numRequestedBytes := int64(10)
   395  
   396  	testCases := []struct {
   397  		name      string
   398  		expected  error
   399  		contents  string
   400  		skipGzip  bool
   401  		sizeLimit int64
   402  		action    func(api.Artifact) error
   403  	}{
   404  		{
   405  			name:     "ReadAll",
   406  			expected: lenses.ErrFileTooLarge,
   407  			action: func(a api.Artifact) error {
   408  				_, err := a.ReadAll()
   409  				return err
   410  			},
   411  		},
   412  		{
   413  			name:     "ReadAt",
   414  			expected: lenses.ErrRequestSizeTooLarge,
   415  			action: func(a api.Artifact) error {
   416  				buf := make([]byte, numRequestedBytes)
   417  				_, err := a.ReadAt(buf, 3)
   418  				return err
   419  			},
   420  		},
   421  		{
   422  			name:     "ReadAtMost",
   423  			expected: lenses.ErrRequestSizeTooLarge,
   424  			action: func(a api.Artifact) error {
   425  				_, err := a.ReadAtMost(numRequestedBytes)
   426  				return err
   427  			},
   428  		},
   429  		{
   430  			name:     "ReadTail",
   431  			expected: lenses.ErrRequestSizeTooLarge,
   432  			action: func(a api.Artifact) error {
   433  				_, err := a.ReadTail(numRequestedBytes)
   434  				return err
   435  			},
   436  		},
   437  	}
   438  	for _, tc := range testCases {
   439  		t.Run(tc.name+"_NoError", func(nested *testing.T) {
   440  			sizeLimit := int64(2 * len(contents))
   441  			artifact, err := NewPodLogArtifact("job-name", "build-id", "log-name", "container-name", sizeLimit, &fakeAgent{contents: contents})
   442  			if err != nil {
   443  				nested.Fatalf("error creating test data: %s", err)
   444  			}
   445  
   446  			actual := tc.action(artifact)
   447  			if actual != nil {
   448  				nested.Fatalf("unexpected error: %s", actual)
   449  			}
   450  		})
   451  		t.Run(tc.name+"_WithError", func(nested *testing.T) {
   452  			sizeLimit := int64(5)
   453  			artifact, err := NewPodLogArtifact("job-name", "build-id", "log-name", "container-name", sizeLimit, &fakeAgent{contents: contents})
   454  			if err != nil {
   455  				nested.Fatalf("error creating test data: %s", err)
   456  			}
   457  
   458  			actual := tc.action(artifact)
   459  			if actual == nil {
   460  				nested.Fatalf("expected error (%s), but got: nil", tc.expected)
   461  			} else if tc.expected.Error() != actual.Error() {
   462  				nested.Fatalf("expected error (%s), but got: %s", tc.expected, actual)
   463  			}
   464  		})
   465  	}
   466  }