github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/spyglass/spyglass_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  	"fmt"
    21  	"os"
    22  	"strings"
    23  	"testing"
    24  
    25  	"github.com/fsouza/fake-gcs-server/fakestorage"
    26  	"github.com/sirupsen/logrus"
    27  
    28  	"k8s.io/test-infra/prow/config"
    29  	"k8s.io/test-infra/prow/deck/jobs"
    30  	"k8s.io/test-infra/prow/kube"
    31  	"k8s.io/test-infra/prow/spyglass/lenses"
    32  )
    33  
    34  var (
    35  	fakeJa        *jobs.JobAgent
    36  	fakeGCSServer *fakestorage.Server
    37  )
    38  
    39  const (
    40  	testSrc = "gs://test-bucket/logs/example-ci-run/403"
    41  )
    42  
    43  type fkc []kube.ProwJob
    44  
    45  func (f fkc) GetLog(pod string) ([]byte, error) {
    46  	return nil, nil
    47  }
    48  
    49  func (f fkc) ListPods(selector string) ([]kube.Pod, error) {
    50  	return nil, nil
    51  }
    52  
    53  func (f fkc) ListProwJobs(s string) ([]kube.ProwJob, error) {
    54  	return f, nil
    55  }
    56  
    57  type fpkc string
    58  
    59  func (f fpkc) GetLog(pod string) ([]byte, error) {
    60  	if pod == "wowowow" || pod == "powowow" {
    61  		return []byte(f), nil
    62  	}
    63  	return nil, fmt.Errorf("pod not found: %s", pod)
    64  }
    65  
    66  func (f fpkc) GetContainerLog(pod, container string) ([]byte, error) {
    67  	if pod == "wowowow" || pod == "powowow" {
    68  		return []byte(f), nil
    69  	}
    70  	return nil, fmt.Errorf("pod not found: %s", pod)
    71  }
    72  
    73  func (f fpkc) GetLogTail(pod, container string, n int64) ([]byte, error) {
    74  	if pod == "wowowow" || pod == "powowow" {
    75  		tailBytes := []byte(f)
    76  		lenTailBytes := int64(len(tailBytes))
    77  		if lenTailBytes < n {
    78  			return tailBytes, nil
    79  		}
    80  		return tailBytes[lenTailBytes-n-1:], nil
    81  	}
    82  	return nil, fmt.Errorf("pod not found: %s", pod)
    83  }
    84  
    85  type fca struct {
    86  	c config.Config
    87  }
    88  
    89  func (ca fca) Config() *config.Config {
    90  	return &ca.c
    91  }
    92  
    93  func TestMain(m *testing.M) {
    94  	var longLog string
    95  	for i := 0; i < 300; i++ {
    96  		longLog += "here a log\nthere a log\neverywhere a log log\n"
    97  	}
    98  	fakeGCSServer = fakestorage.NewServer([]fakestorage.Object{
    99  		{
   100  			BucketName: "test-bucket",
   101  			Name:       "logs/example-ci-run/403/build-log.txt",
   102  			Content:    []byte("Oh wow\nlogs\nthis is\ncrazy"),
   103  		},
   104  		{
   105  			BucketName: "test-bucket",
   106  			Name:       "logs/example-ci-run/403/long-log.txt",
   107  			Content:    []byte(longLog),
   108  		},
   109  		{
   110  			BucketName: "test-bucket",
   111  			Name:       "logs/example-ci-run/403/junit_01.xml",
   112  			Content: []byte(`<testsuite tests="1017" failures="1017" time="0.016981535">
   113  <testcase name="BeforeSuite" classname="Kubernetes e2e suite" time="0.006343795">
   114  <failure type="Failure">
   115  test/e2e/e2e.go:137 BeforeSuite on Node 1 failed test/e2e/e2e.go:137
   116  </failure>
   117  </testcase>
   118  </testsuite>`),
   119  		},
   120  		{
   121  			BucketName: "test-bucket",
   122  			Name:       "logs/example-ci-run/403/started.json",
   123  			Content: []byte(`{
   124  						  "node": "gke-prow-default-pool-3c8994a8-qfhg", 
   125  						  "repo-version": "v1.12.0-alpha.0.985+e6f64d0a79243c", 
   126  						  "timestamp": 1528742858, 
   127  						  "repos": {
   128  						    "k8s.io/kubernetes": "master", 
   129  						    "k8s.io/release": "master"
   130  						  }, 
   131  						  "version": "v1.12.0-alpha.0.985+e6f64d0a79243c", 
   132  						  "metadata": {
   133  						    "pod": "cbc53d8e-6da7-11e8-a4ff-0a580a6c0269"
   134  						  }
   135  						}`),
   136  		},
   137  		{
   138  			BucketName: "test-bucket",
   139  			Name:       "logs/example-ci-run/403/finished.json",
   140  			Content: []byte(`{
   141  						  "timestamp": 1528742943, 
   142  						  "version": "v1.12.0-alpha.0.985+e6f64d0a79243c", 
   143  						  "result": "SUCCESS", 
   144  						  "passed": true, 
   145  						  "job-version": "v1.12.0-alpha.0.985+e6f64d0a79243c", 
   146  						  "metadata": {
   147  						    "repo": "k8s.io/kubernetes", 
   148  						    "repos": {
   149  						      "k8s.io/kubernetes": "master", 
   150  						      "k8s.io/release": "master"
   151  						    }, 
   152  						    "infra-commit": "260081852", 
   153  						    "pod": "cbc53d8e-6da7-11e8-a4ff-0a580a6c0269", 
   154  						    "repo-commit": "e6f64d0a79243c834babda494151fc5d66582240"
   155  						  },
   156  						},`),
   157  		},
   158  	})
   159  	defer fakeGCSServer.Stop()
   160  	kc := fkc{
   161  		kube.ProwJob{
   162  			Spec: kube.ProwJobSpec{
   163  				Agent: kube.KubernetesAgent,
   164  				Job:   "job",
   165  			},
   166  			Status: kube.ProwJobStatus{
   167  				PodName: "wowowow",
   168  				BuildID: "123",
   169  			},
   170  		},
   171  		kube.ProwJob{
   172  			Spec: kube.ProwJobSpec{
   173  				Agent:   kube.KubernetesAgent,
   174  				Job:     "jib",
   175  				Cluster: "trusted",
   176  			},
   177  			Status: kube.ProwJobStatus{
   178  				PodName: "powowow",
   179  				BuildID: "123",
   180  			},
   181  		},
   182  	}
   183  	fakeJa = jobs.NewJobAgent(kc, map[string]jobs.PodLogClient{kube.DefaultClusterAlias: fpkc("clusterA"), "trusted": fpkc("clusterB")}, &config.Agent{})
   184  	fakeJa.Start()
   185  	os.Exit(m.Run())
   186  }
   187  
   188  type dumpLens struct{}
   189  
   190  func (dumpLens) Name() string {
   191  	return "dump"
   192  }
   193  
   194  func (dumpLens) Title() string {
   195  	return "Dump View"
   196  }
   197  
   198  func (dumpLens) Priority() int {
   199  	return 1
   200  }
   201  
   202  func (dumpLens) Header(artifacts []lenses.Artifact, resourceDir string) string {
   203  	return ""
   204  }
   205  
   206  func (dumpLens) Body(artifacts []lenses.Artifact, resourceDir, data string) string {
   207  	var view []byte
   208  	for _, a := range artifacts {
   209  		data, err := a.ReadAll()
   210  		if err != nil {
   211  			logrus.WithError(err).Error("Error reading artifact")
   212  			continue
   213  		}
   214  		view = append(view, data...)
   215  	}
   216  	return string(view)
   217  }
   218  
   219  func (dumpLens) Callback(artifacts []lenses.Artifact, resourceDir, data string) string {
   220  	return ""
   221  }
   222  
   223  func TestViews(t *testing.T) {
   224  	fakeGCSClient := fakeGCSServer.Client()
   225  	testCases := []struct {
   226  		name               string
   227  		registeredViewers  []lenses.Lens
   228  		matchCache         map[string][]string
   229  		expectedLensTitles []string
   230  	}{
   231  		{
   232  			name:              "Spyglass basic test",
   233  			registeredViewers: []lenses.Lens{dumpLens{}},
   234  			matchCache: map[string][]string{
   235  				"dump": {"started.json"},
   236  			},
   237  			expectedLensTitles: []string{"Dump View"},
   238  		},
   239  		{
   240  			name:              "Spyglass no matches",
   241  			registeredViewers: []lenses.Lens{dumpLens{}},
   242  			matchCache: map[string][]string{
   243  				"dump": {},
   244  			},
   245  		},
   246  	}
   247  
   248  	for _, tc := range testCases {
   249  		t.Run(tc.name, func(t *testing.T) {
   250  			for _, l := range tc.registeredViewers {
   251  				lenses.RegisterLens(l)
   252  			}
   253  			sg := New(fakeJa, &config.Agent{}, fakeGCSClient)
   254  			lenses := sg.Lenses(tc.matchCache)
   255  			for _, l := range lenses {
   256  				var found bool
   257  				for _, title := range tc.expectedLensTitles {
   258  					if title == l.Title() {
   259  						found = true
   260  					}
   261  				}
   262  				if !found {
   263  					t.Errorf("lens title %s not found in expected titles.", l.Title())
   264  				}
   265  			}
   266  			for _, title := range tc.expectedLensTitles {
   267  				var found bool
   268  				for _, l := range lenses {
   269  					if title == l.Title() {
   270  						found = true
   271  					}
   272  				}
   273  				if !found {
   274  					t.Errorf("expected title %s not found in produced lenses.", title)
   275  				}
   276  			}
   277  		})
   278  	}
   279  }
   280  
   281  func TestSplitSrc(t *testing.T) {
   282  	testCases := []struct {
   283  		name       string
   284  		src        string
   285  		expKeyType string
   286  		expKey     string
   287  		expError   bool
   288  	}{
   289  		{
   290  			name:     "empty string",
   291  			src:      "",
   292  			expError: true,
   293  		},
   294  		{
   295  			name:     "missing key",
   296  			src:      "gcs",
   297  			expError: true,
   298  		},
   299  		{
   300  			name:       "prow key",
   301  			src:        "prowjob/example-job-name/123456",
   302  			expKeyType: "prowjob",
   303  			expKey:     "example-job-name/123456",
   304  		},
   305  		{
   306  			name:       "gcs key",
   307  			src:        "gcs/kubernetes-jenkins/pr-logs/pull/test-infra/0000/example-job-name/314159/",
   308  			expKeyType: "gcs",
   309  			expKey:     "kubernetes-jenkins/pr-logs/pull/test-infra/0000/example-job-name/314159/",
   310  		},
   311  	}
   312  	for _, tc := range testCases {
   313  		keyType, key, err := splitSrc(tc.src)
   314  		if tc.expError && err == nil {
   315  			t.Errorf("test %q expected error", tc.name)
   316  		}
   317  		if !tc.expError && err != nil {
   318  			t.Errorf("test %q encountered unexpected error: %v", tc.name, err)
   319  		}
   320  		if keyType != tc.expKeyType || key != tc.expKey {
   321  			t.Errorf("test %q: splitting src %q: Expected <%q, %q>, got <%q, %q>",
   322  				tc.name, tc.src, tc.expKeyType, tc.expKey, keyType, key)
   323  		}
   324  	}
   325  }
   326  
   327  func TestJobPath(t *testing.T) {
   328  	kc := fkc{
   329  		kube.ProwJob{
   330  			Spec: kube.ProwJobSpec{
   331  				Type: kube.PeriodicJob,
   332  				Job:  "example-periodic-job",
   333  				DecorationConfig: &kube.DecorationConfig{
   334  					GCSConfiguration: &kube.GCSConfiguration{
   335  						Bucket: "chum-bucket",
   336  					},
   337  				},
   338  			},
   339  			Status: kube.ProwJobStatus{
   340  				PodName: "flying-whales",
   341  				BuildID: "1111",
   342  			},
   343  		},
   344  		kube.ProwJob{
   345  			Spec: kube.ProwJobSpec{
   346  				Type: kube.PresubmitJob,
   347  				Job:  "example-presubmit-job",
   348  				DecorationConfig: &kube.DecorationConfig{
   349  					GCSConfiguration: &kube.GCSConfiguration{
   350  						Bucket: "chum-bucket",
   351  					},
   352  				},
   353  			},
   354  			Status: kube.ProwJobStatus{
   355  				PodName: "flying-whales",
   356  				BuildID: "2222",
   357  			},
   358  		},
   359  		kube.ProwJob{
   360  			Spec: kube.ProwJobSpec{
   361  				Type: kube.PresubmitJob,
   362  				Job:  "undecorated-job",
   363  			},
   364  			Status: kube.ProwJobStatus{
   365  				PodName: "flying-whales",
   366  				BuildID: "1",
   367  			},
   368  		},
   369  		kube.ProwJob{
   370  			Spec: kube.ProwJobSpec{
   371  				Type:             kube.PresubmitJob,
   372  				Job:              "missing-gcs-job",
   373  				DecorationConfig: &kube.DecorationConfig{},
   374  			},
   375  			Status: kube.ProwJobStatus{
   376  				PodName: "flying-whales",
   377  				BuildID: "1",
   378  			},
   379  		},
   380  	}
   381  	fakeJa = jobs.NewJobAgent(kc, map[string]jobs.PodLogClient{kube.DefaultClusterAlias: fpkc("clusterA"), "trusted": fpkc("clusterB")}, &config.Agent{})
   382  	fakeJa.Start()
   383  	testCases := []struct {
   384  		name       string
   385  		src        string
   386  		expJobPath string
   387  		expError   bool
   388  	}{
   389  		{
   390  			name:       "non-presubmit job in GCS with trailing /",
   391  			src:        "gcs/kubernetes-jenkins/logs/example-job-name/123/",
   392  			expJobPath: "kubernetes-jenkins/logs/example-job-name",
   393  		},
   394  		{
   395  			name:       "non-presubmit job in GCS without trailing /",
   396  			src:        "gcs/kubernetes-jenkins/logs/example-job-name/123",
   397  			expJobPath: "kubernetes-jenkins/logs/example-job-name",
   398  		},
   399  		{
   400  			name:       "presubmit job in GCS with trailing /",
   401  			src:        "gcs/kubernetes-jenkins/pr-logs/pull/test-infra/0000/example-job-name/314159/",
   402  			expJobPath: "kubernetes-jenkins/pr-logs/directory/example-job-name",
   403  		},
   404  		{
   405  			name:       "presubmit job in GCS without trailing /",
   406  			src:        "gcs/kubernetes-jenkins/pr-logs/pull/test-infra/0000/example-job-name/314159",
   407  			expJobPath: "kubernetes-jenkins/pr-logs/directory/example-job-name",
   408  		},
   409  		{
   410  			name:       "non-presubmit Prow job",
   411  			src:        "prowjob/example-periodic-job/1111",
   412  			expJobPath: "chum-bucket/logs/example-periodic-job",
   413  		},
   414  		{
   415  			name:       "Prow presubmit job",
   416  			src:        "prowjob/example-presubmit-job/2222",
   417  			expJobPath: "chum-bucket/pr-logs/directory/example-presubmit-job",
   418  		},
   419  		{
   420  			name:     "nonexistent job",
   421  			src:      "prowjob/example-periodic-job/0000",
   422  			expError: true,
   423  		},
   424  		{
   425  			name:     "invalid key type",
   426  			src:      "oh/my/glob/drama/bomb",
   427  			expError: true,
   428  		},
   429  		{
   430  			name:     "invalid GCS path",
   431  			src:      "gcs/kubernetes-jenkins/bad-path",
   432  			expError: true,
   433  		},
   434  		{
   435  			name:     "job missing decoration",
   436  			src:      "prowjob/undecorated-job/1",
   437  			expError: true,
   438  		},
   439  		{
   440  			name:     "job missing GCS config",
   441  			src:      "prowjob/missing-gcs-job/1",
   442  			expError: true,
   443  		},
   444  	}
   445  	for _, tc := range testCases {
   446  		fakeGCSClient := fakeGCSServer.Client()
   447  		sg := New(fakeJa, &config.Agent{}, fakeGCSClient)
   448  		jobPath, err := sg.JobPath(tc.src)
   449  		if tc.expError && err == nil {
   450  			t.Errorf("test %q: JobPath(%q) expected error", tc.name, tc.src)
   451  			continue
   452  		}
   453  		if !tc.expError && err != nil {
   454  			t.Errorf("test %q: JobPath(%q) returned unexpected error %v", tc.name, tc.src, err)
   455  			continue
   456  		}
   457  		if jobPath != tc.expJobPath {
   458  			t.Errorf("test %q: JobPath(%q) expected %q, got %q", tc.name, tc.src, tc.expJobPath, jobPath)
   459  		}
   460  	}
   461  }
   462  
   463  func TestProwToGCS(t *testing.T) {
   464  	kc := fkc{
   465  		kube.ProwJob{
   466  			Spec: kube.ProwJobSpec{
   467  				Job: "gubernator-job",
   468  			},
   469  			Status: kube.ProwJobStatus{
   470  				URL:     "https://gubernator.example.com/build/some-bucket/gubernator-job/1111/",
   471  				BuildID: "1111",
   472  			},
   473  		},
   474  		kube.ProwJob{
   475  			Spec: kube.ProwJobSpec{
   476  				Job: "spyglass-job",
   477  			},
   478  			Status: kube.ProwJobStatus{
   479  				URL:     "https://prow.example.com/view/gcs/some-bucket/spyglass-job/2222/",
   480  				BuildID: "2222",
   481  			},
   482  		},
   483  	}
   484  	fakeJa = jobs.NewJobAgent(kc, map[string]jobs.PodLogClient{kube.DefaultClusterAlias: fpkc("clusterA"), "trusted": fpkc("clusterB")}, &config.Agent{})
   485  	fakeJa.Start()
   486  
   487  	testCases := []struct {
   488  		name         string
   489  		key          string
   490  		configPrefix string
   491  		expectedPath string
   492  		expectError  bool
   493  	}{
   494  		{
   495  			name:         "extraction from gubernator-like URL",
   496  			key:          "gubernator-job/1111",
   497  			configPrefix: "https://gubernator.example.com/build/",
   498  			expectedPath: "some-bucket/gubernator-job/1111/",
   499  			expectError:  false,
   500  		},
   501  		{
   502  			name:         "extraction from spyglass-like URL",
   503  			key:          "spyglass-job/2222",
   504  			configPrefix: "https://prow.example.com/view/gcs/",
   505  			expectedPath: "some-bucket/spyglass-job/2222/",
   506  			expectError:  false,
   507  		},
   508  		{
   509  			name:         "failed extraction from wrong URL",
   510  			key:          "spyglass-job/1111",
   511  			configPrefix: "https://gubernator.example.com/build/",
   512  			expectedPath: "",
   513  			expectError:  true,
   514  		},
   515  		{
   516  			name:         "prefix longer than URL",
   517  			key:          "spyglass-job/2222",
   518  			configPrefix: strings.Repeat("!", 100),
   519  			expectError:  true,
   520  		},
   521  	}
   522  
   523  	for _, tc := range testCases {
   524  		fakeGCSClient := fakeGCSServer.Client()
   525  		fakeConfigAgent := fca{
   526  			c: config.Config{
   527  				ProwConfig: config.ProwConfig{
   528  					Plank: config.Plank{
   529  						JobURLPrefix: tc.configPrefix,
   530  					},
   531  				},
   532  			},
   533  		}
   534  		sg := New(fakeJa, fakeConfigAgent, fakeGCSClient)
   535  
   536  		p, err := sg.prowToGCS(tc.key)
   537  		if err != nil && !tc.expectError {
   538  			t.Errorf("test %q: unexpected error: %v", tc.key, err)
   539  			continue
   540  		}
   541  		if err == nil && tc.expectError {
   542  			t.Errorf("test %q: expected an error but instead got success and path '%s'", tc.key, p)
   543  			continue
   544  		}
   545  		if p != tc.expectedPath {
   546  			t.Errorf("test %q: expected '%s' but got '%s'", tc.key, tc.expectedPath, p)
   547  		}
   548  	}
   549  }
   550  
   551  func TestFetchArtifactsPodLog(t *testing.T) {
   552  	kc := fkc{
   553  		kube.ProwJob{
   554  			Spec: kube.ProwJobSpec{
   555  				Agent: kube.KubernetesAgent,
   556  				Job:   "job",
   557  			},
   558  			Status: kube.ProwJobStatus{
   559  				PodName: "wowowow",
   560  				BuildID: "123",
   561  				URL:     "https://gubernator.example.com/build/job/123",
   562  			},
   563  		},
   564  	}
   565  	fakeConfigAgent := fca{
   566  		c: config.Config{
   567  			ProwConfig: config.ProwConfig{
   568  				Plank: config.Plank{
   569  					JobURLPrefix: "https://gubernator.example.com/build/",
   570  				},
   571  			},
   572  		},
   573  	}
   574  	fakeJa = jobs.NewJobAgent(kc, map[string]jobs.PodLogClient{kube.DefaultClusterAlias: fpkc("clusterA")}, &config.Agent{})
   575  	fakeJa.Start()
   576  
   577  	fakeGCSClient := fakeGCSServer.Client()
   578  
   579  	sg := New(fakeJa, fakeConfigAgent, fakeGCSClient)
   580  	testKeys := []string{
   581  		"prowjob/job/123",
   582  		"gcs/kubernetes-jenkins/logs/job/123/",
   583  		"gcs/kubernetes-jenkins/logs/job/123",
   584  	}
   585  
   586  	for _, key := range testKeys {
   587  		result, err := sg.FetchArtifacts(key, "", 500e6, []string{"build-log.txt"})
   588  		if err != nil {
   589  			t.Errorf("Unexpected error grabbing pod log for %s: %v", key, err)
   590  			continue
   591  		}
   592  		if len(result) != 1 {
   593  			t.Errorf("Expected 1 artifact for %s, got %d", key, len(result))
   594  			continue
   595  		}
   596  		content, err := result[0].ReadAll()
   597  		if err != nil {
   598  			t.Errorf("Unexpected error reading pod log for %s: %v", key, err)
   599  			continue
   600  		}
   601  		if string(content) != "clusterA" {
   602  			t.Errorf("Bad pod log content for %s: %q (expected 'clusterA')", key, content)
   603  		}
   604  	}
   605  }