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 }