-
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.
- Loading branch information
1 parent
7c0fc42
commit 6da3657
Showing
1 changed file
with
194 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,194 @@ | ||
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 | ||
var listener net.Listener | ||
var registry *prometheus.Registry | ||
var 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) | ||
} | ||
}() | ||
}) | ||
|
||
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() { | ||
// Wait for the server to start | ||
time.Sleep(100 * time.Millisecond) | ||
|
||
// Send an HTTP GET request to the metrics endpoint | ||
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 the response body | ||
body, err := io.ReadAll(resp.Body) | ||
Expect(err).NotTo(HaveOccurred()) | ||
Expect(resp.Body.Close()).To(Succeed()) | ||
|
||
metricsOutput := string(body) | ||
|
||
// Validate gRPC metrics | ||
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`)) | ||
|
||
// Validate cosi_requests_total metric | ||
Expect(metricsOutput).To(ContainSubstring(`cosi_requests_total{method="GET",status="200"} 1`)) | ||
}) | ||
|
||
It("should gracefully shutdown the server", func() { | ||
err := server.Close() | ||
Expect(err).NotTo(HaveOccurred()) | ||
}) | ||
|
||
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() { | ||
time.Sleep(100 * time.Millisecond) | ||
|
||
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() { | ||
time.Sleep(100 * time.Millisecond) | ||
|
||
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 | ||
}() | ||
} | ||
|
||
// Wait for all requests to complete | ||
for i := 0; i < numRequests; i++ { | ||
<-done | ||
} | ||
}) | ||
|
||
It("should expose additional custom metrics", func() { | ||
// Register a custom metric | ||
customCounter := prometheus.NewCounter(prometheus.CounterOpts{ | ||
Name: "custom_metric_total", | ||
Help: "A custom metric for testing purposes.", | ||
}) | ||
Expect(registry.Register(customCounter)).To(Succeed()) | ||
|
||
// Increment the custom metric | ||
customCounter.Add(42) | ||
|
||
time.Sleep(100 * time.Millisecond) | ||
|
||
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 the custom metric | ||
body, err := io.ReadAll(resp.Body) | ||
Expect(err).NotTo(HaveOccurred()) | ||
Expect(resp.Body.Close()).To(Succeed()) | ||
|
||
metricsOutput := string(body) | ||
Expect(metricsOutput).To(ContainSubstring(`custom_metric_total 42`)) | ||
}) | ||
|
||
}) |