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 }