github.com/cilium/cilium@v1.16.2/operator/api/metrics_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package api
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"fmt"
    10  	"net/http"
    11  	"net/http/httptest"
    12  	"reflect"
    13  	"testing"
    14  
    15  	"github.com/cilium/hive/cell"
    16  	"github.com/cilium/hive/hivetest"
    17  	"github.com/go-openapi/runtime"
    18  	"go.uber.org/goleak"
    19  
    20  	"github.com/cilium/cilium/api/v1/operator/models"
    21  	"github.com/cilium/cilium/api/v1/operator/server/restapi/metrics"
    22  	operatorMetrics "github.com/cilium/cilium/operator/metrics"
    23  	"github.com/cilium/cilium/pkg/hive"
    24  	cellMetric "github.com/cilium/cilium/pkg/metrics"
    25  	"github.com/cilium/cilium/pkg/metrics/metric"
    26  	"github.com/cilium/cilium/pkg/safeio"
    27  )
    28  
    29  func TestMetricsHandlerWithoutMetrics(t *testing.T) {
    30  	defer goleak.VerifyNone(
    31  		t,
    32  		// ignore goroutine started from sigs.k8s.io/controller-runtime/pkg/log.go init function
    33  		goleak.IgnoreTopFunction("time.Sleep"),
    34  	)
    35  
    36  	rr := httptest.NewRecorder()
    37  
    38  	hive := hive.New(
    39  		operatorMetrics.Cell,
    40  		cell.Provide(func() operatorMetrics.SharedConfig {
    41  			return operatorMetrics.SharedConfig{
    42  				EnableMetrics:    false,
    43  				EnableGatewayAPI: false,
    44  			}
    45  		}),
    46  
    47  		MetricsHandlerCell,
    48  
    49  		// transform GetMetricsHandler in a http.HandlerFunc to use
    50  		// the http package testing facilities
    51  		cell.Provide(func(h metrics.GetMetricsHandler) http.HandlerFunc {
    52  			return func(w http.ResponseWriter, r *http.Request) {
    53  				res := h.Handle(metrics.GetMetricsParams{})
    54  				res.WriteResponse(w, runtime.TextProducer())
    55  			}
    56  		}),
    57  
    58  		cell.Invoke(func(hf http.HandlerFunc) {
    59  			req := httptest.NewRequest(http.MethodGet, "http://localhost/metrics", nil)
    60  			hf.ServeHTTP(rr, req)
    61  		}),
    62  	)
    63  
    64  	tlog := hivetest.Logger(t)
    65  	if err := hive.Start(tlog, context.Background()); err != nil {
    66  		t.Fatalf("failed to start: %s", err)
    67  	}
    68  
    69  	if rr.Result().StatusCode != http.StatusOK {
    70  		t.Fatalf("expected http status code %d, got %d", http.StatusOK, rr.Result().StatusCode)
    71  	}
    72  
    73  	body, err := safeio.ReadAllLimit(rr.Result().Body, safeio.KB)
    74  	if err != nil {
    75  		t.Fatalf("error while reading response body: %s", err)
    76  	}
    77  	rr.Result().Body.Close()
    78  
    79  	var metrics []models.Metric
    80  	if err := json.Unmarshal(body, &metrics); err != nil {
    81  		t.Fatalf("error while unmarshaling response body: %s", err)
    82  	}
    83  
    84  	if len(metrics) != 0 {
    85  		t.Fatalf("no metrics expected, found %v", metrics)
    86  	}
    87  
    88  	if err := hive.Stop(tlog, context.Background()); err != nil {
    89  		t.Fatalf("failed to stop: %s", err)
    90  	}
    91  }
    92  
    93  func TestMetricsHandlerWithMetrics(t *testing.T) {
    94  	defer goleak.VerifyNone(
    95  		t,
    96  		// ignore goroutine started from sigs.k8s.io/controller-runtime/pkg/log.go init function
    97  		goleak.IgnoreTopFunction("time.Sleep"),
    98  	)
    99  
   100  	rr := httptest.NewRecorder()
   101  
   102  	hive := hive.New(
   103  		operatorMetrics.Cell,
   104  		cell.Provide(func() operatorMetrics.SharedConfig {
   105  			return operatorMetrics.SharedConfig{
   106  				EnableMetrics:    true,
   107  				EnableGatewayAPI: false,
   108  			}
   109  		}),
   110  		cellMetric.Metric(newTestMetrics),
   111  
   112  		MetricsHandlerCell,
   113  
   114  		// transform GetMetricsHandler in a http.HandlerFunc to use
   115  		// the http package testing facilities
   116  		cell.Provide(func(h metrics.GetMetricsHandler) http.HandlerFunc {
   117  			return func(w http.ResponseWriter, r *http.Request) {
   118  				res := h.Handle(metrics.GetMetricsParams{})
   119  				res.WriteResponse(w, runtime.TextProducer())
   120  			}
   121  		}),
   122  
   123  		cell.Invoke(func(lc cell.Lifecycle, metrics *testMetrics, hf http.HandlerFunc) {
   124  			lc.Append(cell.Hook{
   125  				OnStart: func(cell.HookContext) error {
   126  					// set values for some metrics
   127  					metrics.MetricA.
   128  						WithLabelValues("success").
   129  						Inc()
   130  
   131  					req := httptest.NewRequest(http.MethodGet, "http://localhost/metrics", nil)
   132  					hf.ServeHTTP(rr, req)
   133  
   134  					return nil
   135  				},
   136  			})
   137  		}),
   138  	)
   139  
   140  	// Enabling the metrics for the operator will also start the prometheus server.
   141  	// To avoid port clashing while testing, let the kernel pick an available port.
   142  	hive.Viper().Set(operatorMetrics.OperatorPrometheusServeAddr, "localhost:0")
   143  
   144  	tlog := hivetest.Logger(t)
   145  	if err := hive.Start(tlog, context.Background()); err != nil {
   146  		t.Fatalf("failed to start: %s", err)
   147  	}
   148  
   149  	if rr.Result().StatusCode != http.StatusOK {
   150  		t.Fatalf("expected http status code %d, got %d", http.StatusOK, rr.Result().StatusCode)
   151  	}
   152  
   153  	body, err := safeio.ReadAllLimit(rr.Result().Body, safeio.MB)
   154  	if err != nil {
   155  		t.Fatalf("error while reading response body: %s", err)
   156  	}
   157  	rr.Result().Body.Close()
   158  
   159  	var metrics []models.Metric
   160  	if err := json.Unmarshal(body, &metrics); err != nil {
   161  		t.Fatalf("error while unmarshaling response body: %s", err)
   162  	}
   163  
   164  	if err := testMetric(
   165  		metrics,
   166  		"operator_api_metrics_test_metric_a",
   167  		float64(1),
   168  		map[string]string{
   169  			"outcome": "success",
   170  		},
   171  	); err != nil {
   172  		t.Fatalf("error while inspecting metric: %s", err)
   173  	}
   174  
   175  	if err := hive.Stop(tlog, context.Background()); err != nil {
   176  		t.Fatalf("failed to stop: %s", err)
   177  	}
   178  }
   179  
   180  func testMetric(metrics []models.Metric, name string, value float64, labels map[string]string,
   181  ) error {
   182  	for _, metric := range metrics {
   183  		if metric.Name == name {
   184  			if metric.Value != value {
   185  				return fmt.Errorf("expected value %f for %q, got %f", value, name, metric.Value)
   186  			}
   187  			if !reflect.DeepEqual(metric.Labels, labels) {
   188  				return fmt.Errorf("expected labels map %v for %q, got %v", labels, name, metric.Labels)
   189  			}
   190  			return nil
   191  		}
   192  	}
   193  	return fmt.Errorf("%q not found", name)
   194  }
   195  
   196  type testMetrics struct {
   197  	MetricA metric.Vec[metric.Counter]
   198  }
   199  
   200  func newTestMetrics() *testMetrics {
   201  	return &testMetrics{
   202  		MetricA: metric.NewCounterVec(metric.CounterOpts{
   203  			Namespace: "operator_api_metrics_test",
   204  			Name:      "metric_a",
   205  		}, []string{"outcome"}),
   206  	}
   207  }