-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
1 changed file
with
175 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
package metrics_test | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"net" | ||
"net/http" | ||
"strings" | ||
"testing" | ||
"time" | ||
|
||
"github.com/scality/cosi-driver/pkg/constants" | ||
"github.com/scality/cosi-driver/pkg/metrics" | ||
|
||
. "github.com/onsi/ginkgo/v2" | ||
. "github.com/onsi/gomega" | ||
"github.com/prometheus/client_golang/prometheus" | ||
"github.com/prometheus/client_golang/prometheus/promhttp" | ||
) | ||
|
||
func TestGRPCFactorySuite(t *testing.T) { | ||
RegisterFailHandler(Fail) | ||
RunSpecs(t, "Metrics Test Suite") | ||
} | ||
|
||
var _ = Describe("Metrics Server", func() { | ||
var ( | ||
server *http.Server | ||
listener net.Listener | ||
registry *prometheus.Registry | ||
grpcMetrics *prometheus.CounterVec | ||
) | ||
|
||
BeforeEach(func() { | ||
// Create a fresh registry | ||
registry = prometheus.NewRegistry() | ||
|
||
// Create and register gRPC metrics with the fresh registry | ||
grpcMetrics = prometheus.NewCounterVec( | ||
prometheus.CounterOpts{ | ||
Name: "grpc_server_started_total", | ||
Help: "Total number of RPCs started on the server.", | ||
}, | ||
[]string{"grpc_method", "grpc_service", "grpc_type"}, | ||
) | ||
Expect(registry.Register(grpcMetrics)).To(Succeed()) | ||
|
||
// Increment gRPC metrics to simulate usage | ||
grpcMetrics.WithLabelValues("DriverGetInfo", "cosi.v1alpha1.Identity", "unary").Add(5) | ||
grpcMetrics.WithLabelValues("DriverCreateBucket", "cosi.v1alpha1.Provisioner", "unary").Add(4) | ||
grpcMetrics.WithLabelValues("DriverDeleteBucket", "cosi.v1alpha1.Provisioner", "unary").Add(3) | ||
grpcMetrics.WithLabelValues("DriverGrantBucketAccess", "cosi.v1alpha1.Provisioner", "unary").Add(2) | ||
grpcMetrics.WithLabelValues("DriverRevokeBucketAccess", "cosi.v1alpha1.Provisioner", "unary").Add(1) | ||
|
||
// Increment the cosi_requests_total counter | ||
Expect(registry.Register(metrics.RequestsTotal)).To(Succeed()) | ||
metrics.RequestsTotal.With(prometheus.Labels{"method": "GET", "status": "200"}).Inc() | ||
|
||
// Wrap the Prometheus handler with the fresh registry | ||
mux := http.NewServeMux() | ||
mux.Handle(constants.MetricsPath, promhttp.HandlerFor(registry, promhttp.HandlerOpts{})) | ||
|
||
// Create and start the metrics server | ||
var err error | ||
server = &http.Server{Handler: mux} | ||
listener, err = net.Listen("tcp", "127.0.0.1:0") | ||
Expect(err).NotTo(HaveOccurred()) | ||
|
||
go func() { | ||
err := server.Serve(listener) | ||
if err != nil && err != http.ErrServerClosed { | ||
panic(err) | ||
} | ||
}() | ||
|
||
// Wait for the server to start | ||
time.Sleep(100 * time.Millisecond) | ||
}) | ||
|
||
AfterEach(func() { | ||
// Shutdown the server if it is still running | ||
if server != nil { | ||
err := server.Close() | ||
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { | ||
Expect(err).NotTo(HaveOccurred()) | ||
} | ||
} | ||
|
||
// Close the listener if it's still open | ||
if listener != nil { | ||
err := listener.Close() | ||
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { | ||
Expect(err).NotTo(HaveOccurred()) | ||
} | ||
} | ||
}) | ||
|
||
It("should expose gRPC metrics on the Prometheus endpoint", func() { | ||
addr := listener.Addr().String() | ||
resp, err := http.Get(fmt.Sprintf("http://%s%s", addr, constants.MetricsPath)) | ||
Expect(err).NotTo(HaveOccurred()) | ||
Expect(resp.StatusCode).To(Equal(http.StatusOK)) | ||
|
||
// Read and validate metrics | ||
body, err := io.ReadAll(resp.Body) | ||
Expect(err).NotTo(HaveOccurred()) | ||
Expect(resp.Body.Close()).To(Succeed()) | ||
metricsOutput := string(body) | ||
|
||
Expect(metricsOutput).To(ContainSubstring(`grpc_server_started_total{grpc_method="DriverGetInfo",grpc_service="cosi.v1alpha1.Identity",grpc_type="unary"} 5`)) | ||
Expect(metricsOutput).To(ContainSubstring(`grpc_server_started_total{grpc_method="DriverCreateBucket",grpc_service="cosi.v1alpha1.Provisioner",grpc_type="unary"} 4`)) | ||
Expect(metricsOutput).To(ContainSubstring(`grpc_server_started_total{grpc_method="DriverDeleteBucket",grpc_service="cosi.v1alpha1.Provisioner",grpc_type="unary"} 3`)) | ||
Expect(metricsOutput).To(ContainSubstring(`grpc_server_started_total{grpc_method="DriverGrantBucketAccess",grpc_service="cosi.v1alpha1.Provisioner",grpc_type="unary"} 2`)) | ||
Expect(metricsOutput).To(ContainSubstring(`grpc_server_started_total{grpc_method="DriverRevokeBucketAccess",grpc_service="cosi.v1alpha1.Provisioner",grpc_type="unary"} 1`)) | ||
Expect(metricsOutput).To(ContainSubstring(`cosi_requests_total{method="GET",status="200"} 1`)) | ||
}) | ||
|
||
It("should gracefully shutdown the server", func() { | ||
Expect(server.Close()).To(Succeed()) | ||
}) | ||
|
||
It("should handle invalid metric labels gracefully", func() { | ||
Expect(func() { | ||
grpcMetrics.WithLabelValues("InvalidMethod", "InvalidService", "InvalidType").Inc() | ||
}).NotTo(Panic()) | ||
}) | ||
|
||
It("should return 404 for invalid metrics paths", func() { | ||
addr := listener.Addr().String() | ||
resp, err := http.Get(fmt.Sprintf("http://%s/invalid-path", addr)) | ||
Expect(err).NotTo(HaveOccurred()) | ||
Expect(resp.StatusCode).To(Equal(http.StatusNotFound)) | ||
Expect(resp.Body.Close()).To(Succeed()) | ||
}) | ||
|
||
It("should handle multiple concurrent requests", func() { | ||
addr := listener.Addr().String() | ||
const numRequests = 10 | ||
done := make(chan bool, numRequests) | ||
|
||
for i := 0; i < numRequests; i++ { | ||
go func() { | ||
resp, err := http.Get(fmt.Sprintf("http://%s%s", addr, constants.MetricsPath)) | ||
Expect(err).NotTo(HaveOccurred()) | ||
Expect(resp.StatusCode).To(Equal(http.StatusOK)) | ||
Expect(resp.Body.Close()).To(Succeed()) | ||
done <- true | ||
}() | ||
} | ||
|
||
for i := 0; i < numRequests; i++ { | ||
<-done | ||
} | ||
}) | ||
|
||
It("should expose additional custom metrics", func() { | ||
customCounter := prometheus.NewCounter(prometheus.CounterOpts{ | ||
Name: "custom_metric_total", | ||
Help: "A custom metric for testing purposes.", | ||
}) | ||
Expect(registry.Register(customCounter)).To(Succeed()) | ||
customCounter.Add(42) | ||
|
||
addr := listener.Addr().String() | ||
resp, err := http.Get(fmt.Sprintf("http://%s%s", addr, constants.MetricsPath)) | ||
Expect(err).NotTo(HaveOccurred()) | ||
Expect(resp.StatusCode).To(Equal(http.StatusOK)) | ||
|
||
// Validate custom metrics | ||
body, err := io.ReadAll(resp.Body) | ||
Expect(err).NotTo(HaveOccurred()) | ||
Expect(resp.Body.Close()).To(Succeed()) | ||
Expect(string(body)).To(ContainSubstring(`custom_metric_total 42`)) | ||
}) | ||
}) |