github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/cmd/exporter/collector_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 main
    18  
    19  import (
    20  	"reflect"
    21  	"testing"
    22  	"time"
    23  
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/labels"
    26  
    27  	"github.com/google/go-cmp/cmp"
    28  	"github.com/prometheus/client_golang/prometheus"
    29  	dto "github.com/prometheus/client_model/go"
    30  	"github.com/sirupsen/logrus"
    31  
    32  	prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1"
    33  )
    34  
    35  func TestKubeLabelsToPrometheusLabels(t *testing.T) {
    36  	testcases := []struct {
    37  		description         string
    38  		labels              map[string]string
    39  		expectedLabelKeys   []string
    40  		expectedLabelValues []string
    41  	}{
    42  		{
    43  			description:         "empty labels",
    44  			labels:              map[string]string{},
    45  			expectedLabelKeys:   []string{},
    46  			expectedLabelValues: []string{},
    47  		},
    48  		{
    49  			description: "labels with infra role",
    50  			labels: map[string]string{
    51  				"ci.openshift.io/role": "infra",
    52  				"created-by-prow":      "true",
    53  				"prow.k8s.io/build-id": "",
    54  				"prow.k8s.io/id":       "35bca360-e085-11e9-8586-0a58ac104c36",
    55  				"prow.k8s.io/job":      "periodic-prow-auto-config-brancher",
    56  				"prow.k8s.io/type":     "periodic",
    57  			},
    58  			expectedLabelKeys: []string{
    59  				"label_ci_openshift_io_role",
    60  				"label_created_by_prow",
    61  				"label_prow_k8s_io_build_id",
    62  				"label_prow_k8s_io_id",
    63  				"label_prow_k8s_io_job",
    64  				"label_prow_k8s_io_type",
    65  			},
    66  			expectedLabelValues: []string{
    67  				"infra",
    68  				"true",
    69  				"",
    70  				"35bca360-e085-11e9-8586-0a58ac104c36",
    71  				"periodic-prow-auto-config-brancher",
    72  				"periodic",
    73  			},
    74  		},
    75  	}
    76  	for _, tc := range testcases {
    77  		t.Run(tc.description, func(t *testing.T) {
    78  			actualLabelKeys, actualLabelValues := kubeLabelsToPrometheusLabels(tc.labels, "label_")
    79  			assertEqual(t, actualLabelKeys, tc.expectedLabelKeys)
    80  			assertEqual(t, actualLabelValues, tc.expectedLabelValues)
    81  		})
    82  	}
    83  }
    84  
    85  func assertEqual(t *testing.T, actual, expected interface{}) {
    86  	if !reflect.DeepEqual(actual, expected) {
    87  		t.Errorf("actual differs from expected:\n%s", cmp.Diff(expected, actual))
    88  	}
    89  }
    90  
    91  type fakeLister struct {
    92  }
    93  
    94  func (l fakeLister) List(selector labels.Selector) ([]*prowapi.ProwJob, error) {
    95  	return []*prowapi.ProwJob{
    96  		{
    97  			Spec: prowapi.ProwJobSpec{
    98  				Agent: prowapi.KubernetesAgent,
    99  				Job:   "pull-test-infra-bazel",
   100  			},
   101  			ObjectMeta: metav1.ObjectMeta{
   102  				Namespace: "default",
   103  				Name:      "7785d7a6-e601-11e9-8512-da8015665453",
   104  				Labels: map[string]string{
   105  					"created-by-prow":          "true",
   106  					"event-GUID":               "770bab40-e601-11e9-8e50-08c45d902b6f",
   107  					"preset-bazel-scratch-dir": "true",
   108  					"preset-service-account":   "true",
   109  					"prow.k8s.io/job":          "pull-test-infra-bazel",
   110  					"prow.k8s.io/refs.org":     "kubernetes",
   111  					"prow.k8s.io/refs.pull":    "14543",
   112  					"prow.k8s.io/refs.repo":    "test-infra",
   113  					"prow.k8s.io/type":         "presubmit",
   114  				},
   115  				Annotations: map[string]string{
   116  					"prow.k8s.io/job":            "pull-test-infra-bazel",
   117  					"testgrid-create-test-group": "true",
   118  				},
   119  			},
   120  		},
   121  		{
   122  			Spec: prowapi.ProwJobSpec{
   123  				Agent: prowapi.KubernetesAgent,
   124  				Job:   "branch-ci-openshift-release-master-config-updates",
   125  			},
   126  			ObjectMeta: metav1.ObjectMeta{
   127  				Namespace: "default",
   128  				Name:      "e44f91e5-e604-11e9-99c1-0a58ac10f9a6",
   129  				Labels: map[string]string{
   130  					"created-by-prow":       "true",
   131  					"event-GUID":            "e4216820-e604-11e9-8cf0-295472589b4f",
   132  					"prow.k8s.io/job":       "branch-ci-openshift-release-master-config-updates",
   133  					"prow.k8s.io/refs.org":  "openshift",
   134  					"prow.k8s.io/refs.repo": "release",
   135  					"prow.k8s.io/type":      "postsubmit",
   136  				},
   137  				Annotations: map[string]string{
   138  					"prow.k8s.io/job": "branch-ci-openshift-release-master-config-updates",
   139  				},
   140  			},
   141  		},
   142  	}, nil
   143  }
   144  
   145  type labelsAndValue struct {
   146  	labels     []*dto.LabelPair
   147  	gaugeValue float64
   148  }
   149  
   150  func TestProwJobCollector(t *testing.T) {
   151  	expected := []labelsAndValue{
   152  		{
   153  			labels: []*dto.LabelPair{
   154  				{
   155  					Name:  stringPointer("job_agent"),
   156  					Value: stringPointer("kubernetes"),
   157  				},
   158  				{
   159  					Name:  stringPointer("job_name"),
   160  					Value: stringPointer("pull-test-infra-bazel"),
   161  				},
   162  				{
   163  					Name:  stringPointer("job_namespace"),
   164  					Value: stringPointer("default"),
   165  				},
   166  				{
   167  					Name:  stringPointer("label_event_GUID"),
   168  					Value: stringPointer("770bab40-e601-11e9-8e50-08c45d902b6f"),
   169  				},
   170  				{
   171  					Name:  stringPointer("label_preset_bazel_scratch_dir"),
   172  					Value: stringPointer("true"),
   173  				},
   174  				{
   175  					Name:  stringPointer("label_preset_service_account"),
   176  					Value: stringPointer("true"),
   177  				},
   178  			},
   179  			gaugeValue: float64(1),
   180  		},
   181  		{
   182  			labels: []*dto.LabelPair{
   183  				{
   184  					Name:  stringPointer("annotation_prow_k8s_io_job"),
   185  					Value: stringPointer("pull-test-infra-bazel"),
   186  				},
   187  				{
   188  					Name:  stringPointer("annotation_testgrid_create_test_group"),
   189  					Value: stringPointer("true"),
   190  				},
   191  				{
   192  					Name:  stringPointer("job_agent"),
   193  					Value: stringPointer("kubernetes"),
   194  				},
   195  				{
   196  					Name:  stringPointer("job_name"),
   197  					Value: stringPointer("pull-test-infra-bazel"),
   198  				},
   199  				{
   200  					Name:  stringPointer("job_namespace"),
   201  					Value: stringPointer("default"),
   202  				},
   203  			},
   204  			gaugeValue: float64(1),
   205  		},
   206  		{
   207  			labels: []*dto.LabelPair{
   208  				{
   209  					Name:  stringPointer("job_agent"),
   210  					Value: stringPointer("kubernetes"),
   211  				},
   212  				{
   213  					Name:  stringPointer("job_name"),
   214  					Value: stringPointer("branch-ci-openshift-release-master-config-updates"),
   215  				},
   216  				{
   217  					Name:  stringPointer("job_namespace"),
   218  					Value: stringPointer("default"),
   219  				},
   220  				{
   221  					Name:  stringPointer("label_event_GUID"),
   222  					Value: stringPointer("e4216820-e604-11e9-8cf0-295472589b4f"),
   223  				},
   224  			},
   225  			gaugeValue: float64(1),
   226  		},
   227  		{
   228  			labels: []*dto.LabelPair{
   229  				{
   230  					Name:  stringPointer("annotation_prow_k8s_io_job"),
   231  					Value: stringPointer("branch-ci-openshift-release-master-config-updates"),
   232  				},
   233  				{
   234  					Name:  stringPointer("job_agent"),
   235  					Value: stringPointer("kubernetes"),
   236  				},
   237  				{
   238  					Name:  stringPointer("job_name"),
   239  					Value: stringPointer("branch-ci-openshift-release-master-config-updates"),
   240  				},
   241  				{
   242  					Name:  stringPointer("job_namespace"),
   243  					Value: stringPointer("default"),
   244  				},
   245  			},
   246  			gaugeValue: float64(1),
   247  		},
   248  	}
   249  
   250  	pjc := prowJobCollector{
   251  		lister: fakeLister{},
   252  	}
   253  	c := make(chan prometheus.Metric)
   254  	go pjc.Collect(c)
   255  
   256  	var metrics []prometheus.Metric
   257  
   258  	for {
   259  		select {
   260  		case msg := <-c:
   261  			metrics = append(metrics, msg)
   262  			logrus.WithField("len(metrics)", len(metrics)).Infof("received a metric")
   263  			if len(metrics) == 4 {
   264  				// will panic when sending more metrics afterwards
   265  				close(c)
   266  				goto ExitForLoop
   267  			}
   268  		case <-time.After(time.Second):
   269  			t.Fatalf("timeout")
   270  		}
   271  	}
   272  
   273  ExitForLoop:
   274  	if len(metrics) != 4 {
   275  		t.Fatalf("unexpected number '%d' of metrics sent by collector", len(metrics))
   276  	}
   277  
   278  	logrus.Info("get all 4 metrics")
   279  
   280  	var actual []labelsAndValue
   281  	for _, metric := range metrics {
   282  		out := &dto.Metric{}
   283  		if err := metric.Write(out); err != nil {
   284  			t.Fatal("unexpected error occurred when writing")
   285  		}
   286  		actual = append(actual, labelsAndValue{labels: out.GetLabel(), gaugeValue: out.GetGauge().GetValue()})
   287  	}
   288  	if equalIgnoreOrder(expected, actual) != true {
   289  		t.Fatalf("equalIgnoreOrder failed")
   290  	}
   291  }
   292  
   293  func equalIgnoreOrder(values1 []labelsAndValue, values2 []labelsAndValue) bool {
   294  	if len(values1) != len(values2) {
   295  		return false
   296  	}
   297  	for _, v1 := range values1 {
   298  		if !contains(values2, v1) {
   299  			logrus.WithField("v1", v1).WithField("values2", values2).Errorf("v1 not in values2")
   300  			return false
   301  		}
   302  	}
   303  	for _, v2 := range values2 {
   304  		if !contains(values1, v2) {
   305  			logrus.WithField("v2", v2).WithField("values1", values1).Errorf("v2 not in values1")
   306  			return false
   307  		}
   308  	}
   309  	return true
   310  }
   311  
   312  func contains(values []labelsAndValue, value labelsAndValue) bool {
   313  	for _, v := range values {
   314  		if reflect.DeepEqual(v.gaugeValue, value.gaugeValue) && reflect.DeepEqual(v.labels, value.labels) {
   315  			return true
   316  		}
   317  	}
   318  	return false
   319  }
   320  
   321  func stringPointer(s string) *string {
   322  	return &s
   323  }
   324  
   325  func TestFilterWithDenylist(t *testing.T) {
   326  	testcases := []struct {
   327  		description string
   328  		labels      map[string]string
   329  		expected    map[string]string
   330  	}{
   331  		{
   332  			description: "nil labels",
   333  			labels:      nil,
   334  			expected:    nil,
   335  		},
   336  		{
   337  			description: "empty labels",
   338  			labels:      map[string]string{},
   339  			expected:    map[string]string{},
   340  		},
   341  		{
   342  			description: "normal labels",
   343  			labels: map[string]string{
   344  				"created-by-prow":       "true",
   345  				"event-GUID":            "770bab40-e601-11e9-8e50-08c45d902b6f",
   346  				"prow.k8s.io/refs.org":  "kubernetes",
   347  				"prow.k8s.io/refs.pull": "14543",
   348  				"prow.k8s.io/refs.repo": "test-infra",
   349  				"prow.k8s.io/type":      "presubmit",
   350  				"ci.openshift.io/role":  "infra",
   351  			},
   352  			expected: map[string]string{
   353  				"event-GUID":           "770bab40-e601-11e9-8e50-08c45d902b6f",
   354  				"ci.openshift.io/role": "infra",
   355  			},
   356  		},
   357  	}
   358  	for _, tc := range testcases {
   359  		t.Run(tc.description, func(t *testing.T) {
   360  			actual := filterWithDenylist(tc.labels)
   361  			assertEqual(t, actual, tc.expected)
   362  		})
   363  	}
   364  }
   365  
   366  func TestGetLatest(t *testing.T) {
   367  	time1 := time.Now()
   368  	time2 := time1.Add(time.Minute)
   369  	time3 := time2.Add(time.Minute)
   370  
   371  	testcases := []struct {
   372  		description string
   373  		jobs        []*prowapi.ProwJob
   374  		expected    map[string]*prowapi.ProwJob
   375  	}{
   376  		{
   377  			description: "nil jobs",
   378  			jobs:        nil,
   379  			expected:    map[string]*prowapi.ProwJob{},
   380  		},
   381  		{
   382  			description: "jobs with or without StartTime",
   383  			jobs: []*prowapi.ProwJob{
   384  				{
   385  					Spec: prowapi.ProwJobSpec{
   386  						Job: "job0",
   387  					},
   388  					Status: prowapi.ProwJobStatus{},
   389  				},
   390  				{
   391  					Spec: prowapi.ProwJobSpec{
   392  						Job: "job1",
   393  					},
   394  					Status: prowapi.ProwJobStatus{StartTime: metav1.Time{Time: time1}},
   395  				},
   396  				{
   397  					Spec: prowapi.ProwJobSpec{
   398  						Job: "job1",
   399  					},
   400  					Status: prowapi.ProwJobStatus{},
   401  				},
   402  				{
   403  					Spec: prowapi.ProwJobSpec{
   404  						Job: "job2",
   405  					},
   406  					Status: prowapi.ProwJobStatus{StartTime: metav1.Time{Time: time1}},
   407  				},
   408  				{
   409  					Spec: prowapi.ProwJobSpec{
   410  						Job: "job2",
   411  					},
   412  					Status: prowapi.ProwJobStatus{StartTime: metav1.Time{Time: time3}},
   413  				},
   414  				{
   415  					Spec: prowapi.ProwJobSpec{
   416  						Job: "job2",
   417  					},
   418  					Status: prowapi.ProwJobStatus{StartTime: metav1.Time{Time: time2}},
   419  				},
   420  				{
   421  					Spec: prowapi.ProwJobSpec{
   422  						Job: "job3",
   423  					},
   424  					Status: prowapi.ProwJobStatus{StartTime: metav1.Time{Time: time3}},
   425  				},
   426  			},
   427  			expected: map[string]*prowapi.ProwJob{
   428  				"job0": {
   429  					Spec: prowapi.ProwJobSpec{
   430  						Job: "job0",
   431  					},
   432  					Status: prowapi.ProwJobStatus{},
   433  				},
   434  				"job1": {
   435  					Spec: prowapi.ProwJobSpec{
   436  						Job: "job1",
   437  					},
   438  					Status: prowapi.ProwJobStatus{StartTime: metav1.Time{Time: time1}},
   439  				},
   440  				"job2": {
   441  					Spec: prowapi.ProwJobSpec{
   442  						Job: "job2",
   443  					},
   444  					Status: prowapi.ProwJobStatus{StartTime: metav1.Time{Time: time3}},
   445  				},
   446  				"job3": {
   447  					Spec: prowapi.ProwJobSpec{
   448  						Job: "job3",
   449  					},
   450  					Status: prowapi.ProwJobStatus{StartTime: metav1.Time{Time: time3}},
   451  				},
   452  			},
   453  		},
   454  	}
   455  	for _, tc := range testcases {
   456  		t.Run(tc.description, func(t *testing.T) {
   457  			actual := getLatest(tc.jobs)
   458  			assertEqual(t, actual, tc.expected)
   459  		})
   460  	}
   461  }