@@ -19,10 +19,18 @@ package function
1919import (
2020 "context"
2121 "fmt"
22+ "io"
23+ "net"
24+ "net/http"
25+ "strings"
2226 "testing"
27+ "time"
2328
2429 "github.com/google/go-cmp/cmp"
2530 "github.com/google/go-cmp/cmp/cmpopts"
31+ "github.com/prometheus/client_golang/prometheus"
32+ "google.golang.org/grpc"
33+ "google.golang.org/grpc/credentials/insecure"
2634 "google.golang.org/protobuf/encoding/protojson"
2735 "google.golang.org/protobuf/testing/protocmp"
2836
@@ -187,3 +195,177 @@ type MockFunctionServer struct {
187195func (s * MockFunctionServer ) RunFunction (context.Context , * v1.RunFunctionRequest ) (* v1.RunFunctionResponse , error ) {
188196 return s .rsp , s .err
189197}
198+
199+ // TestMetricsServer_WithCustomRegistryAndCustomPort verifies that metrics server starts on custom port with custom registry as input.
200+ func TestMetricsServer_WithCustomRegistryAndCustomPort (t * testing.T ) {
201+ // Create mock server
202+ mockServer := & MockFunctionServer {
203+ rsp : & v1.RunFunctionResponse {
204+ Meta : & v1.ResponseMeta {Tag : "traffic-test" },
205+ },
206+ }
207+
208+ // Get ports
209+ grpcPort := getAvailablePort (t )
210+ metricsPort := getAvailablePort (t )
211+
212+ // Start server
213+ serverDone := make (chan error , 1 )
214+ go func () {
215+ err := Serve (mockServer ,
216+ Listen ("tcp" , fmt .Sprintf (":%d" , grpcPort )),
217+ Insecure (true ),
218+ WithMetricsServer (fmt .Sprintf (":%d" , metricsPort )),
219+ WithMetricsRegistry (prometheus .NewRegistry ()),
220+ )
221+ serverDone <- err
222+ }()
223+
224+ // Wait for server to start
225+ time .Sleep (3 * time .Second )
226+
227+ t .Run ("MetricsServerTest On CustomPort With CustomRegistry" , func (t * testing.T ) {
228+ conn , err := grpc .NewClient (fmt .Sprintf ("localhost:%d" , grpcPort ),
229+ grpc .WithTransportCredentials (insecure .NewCredentials ()))
230+ if err != nil {
231+ t .Fatalf ("Failed to connect: %v" , err )
232+ }
233+ defer conn .Close ()
234+
235+ client := v1 .NewFunctionRunnerServiceClient (conn )
236+
237+ // Make the request
238+ req1 := & v1.RunFunctionRequest {
239+ Meta : & v1.RequestMeta {Tag : "request-test" },
240+ }
241+
242+ _ , err = client .RunFunction (context .Background (), req1 )
243+ if err != nil {
244+ t .Errorf ("request failed: %v" , err )
245+ }
246+ // Wait for metrics to be collected
247+ time .Sleep (2 * time .Second )
248+
249+ // Verify metrics endpoint has our custom metrics
250+ metricsURL := fmt .Sprintf ("http://localhost:%d/metrics" , metricsPort )
251+ req , err := http .NewRequestWithContext (context .Background (), http .MethodGet , metricsURL , nil )
252+ if err != nil {
253+ t .Fatalf ("Failed to create request: %v" , err )
254+ }
255+ resp , err := http .DefaultClient .Do (req )
256+ if err != nil {
257+ t .Fatalf ("Failed to get metrics: %v" , err )
258+ }
259+ defer resp .Body .Close ()
260+
261+ body , err := io .ReadAll (resp .Body )
262+ if err != nil {
263+ t .Fatalf ("Failed to read metrics: %v" , err )
264+ }
265+
266+ metricsContent := string (body )
267+
268+ // Verify Prometheus format is working
269+ if ! strings .Contains (metricsContent , "# HELP" ) {
270+ t .Error ("Expected Prometheus format" )
271+ }
272+
273+ // Verify gRPC metrics are present
274+ if ! strings .Contains (metricsContent , "grpc_server_started_total" ) {
275+ t .Error ("Expected grpc_server_started_total metric to be present" )
276+ }
277+ })
278+ }
279+
280+ // TestMetricsServer_WithDefaultRegistryAndDefaultPort verifies that metrics server starts by default on :8080 with default registry with no input.
281+ func TestMetricsServer_WithDefaultRegistryAndDefaultPort (t * testing.T ) {
282+ // Create mock server
283+ mockServer := & MockFunctionServer {
284+ rsp : & v1.RunFunctionResponse {
285+ Meta : & v1.ResponseMeta {Tag : "default-metrics-test" },
286+ },
287+ }
288+
289+ // Get ports
290+ grpcPort := getAvailablePort (t )
291+ // Should use default metrics port 8080
292+ metricsPort := 8080
293+
294+ serverDone := make (chan error , 1 )
295+ go func () {
296+ err := Serve (mockServer ,
297+ Listen ("tcp" , fmt .Sprintf (":%d" , grpcPort )),
298+ Insecure (true ),
299+ )
300+ serverDone <- err
301+ }()
302+
303+ // Wait for server to start
304+ time .Sleep (3 * time .Second )
305+
306+ t .Run ("MetricsServerTest On DefaultPort With DefaultRegisrty" , func (t * testing.T ) {
307+ // Test gRPC connection
308+ conn , err := grpc .NewClient (fmt .Sprintf ("localhost:%d" , grpcPort ),
309+ grpc .WithTransportCredentials (insecure .NewCredentials ()))
310+ if err != nil {
311+ t .Fatalf ("Failed to connect: %v" , err )
312+ }
313+ defer conn .Close ()
314+
315+ client := v1 .NewFunctionRunnerServiceClient (conn )
316+
317+ // Make the request
318+ req := & v1.RunFunctionRequest {
319+ Meta : & v1.RequestMeta {Tag : "default-metrics-test" },
320+ }
321+
322+ _ , err = client .RunFunction (context .Background (), req )
323+ if err != nil {
324+ t .Errorf ("Request failed: %v" , err )
325+ }
326+
327+ // Wait for metrics to be collected
328+ time .Sleep (2 * time .Second )
329+
330+ // Verify metrics endpoint is accessible
331+ metricsURL := fmt .Sprintf ("http://localhost:%d/metrics" , metricsPort )
332+ httpReq , err := http .NewRequestWithContext (context .Background (), http .MethodGet , metricsURL , nil )
333+ if err != nil {
334+ t .Fatalf ("Failed to create request: %v" , err )
335+ }
336+ resp , err := http .DefaultClient .Do (httpReq )
337+ if err != nil {
338+ t .Fatalf ("Failed to get metrics: %v" , err )
339+ }
340+ defer resp .Body .Close ()
341+
342+ body , err := io .ReadAll (resp .Body )
343+ if err != nil {
344+ t .Fatalf ("Failed to read metrics: %v" , err )
345+ }
346+
347+ metricsContent := string (body )
348+
349+ // Verify metrics are present
350+ if ! strings .Contains (metricsContent , "# HELP" ) {
351+ t .Error ("Expected Prometheus format" )
352+ }
353+
354+ // Verify gRPC metrics are present
355+ if ! strings .Contains (metricsContent , "grpc_server_started_total" ) {
356+ t .Error ("Expected grpc_server_started_total metric to be present" )
357+ }
358+ })
359+ }
360+
361+ // Helper function to get an available port.
362+ func getAvailablePort (t * testing.T ) int {
363+ t .Helper ()
364+
365+ listener , err := net .Listen ("tcp" , ":0" )
366+ if err != nil {
367+ t .Fatalf ("Failed to get available port: %v" , err )
368+ }
369+ defer listener .Close ()
370+ return listener .Addr ().(* net.TCPAddr ).Port
371+ }
0 commit comments