From a75907baa011529f06c19f7d4cf3d1c31c222d85 Mon Sep 17 00:00:00 2001 From: Xavier Coulon Date: Tue, 24 Mar 2026 17:34:19 +0100 Subject: [PATCH] test(registration-service): expose metrics for the git commit also, test new `commit` and `short_commit` metrics for host and member operators Signed-off-by: Xavier Coulon --- .govulncheck.yaml | 15 ++++++- test/metrics/metrics_test.go | 72 +++++++++++++++++++++++++++++----- testsupport/init.go | 34 +++++++++++++++- testsupport/metrics/metrics.go | 7 ++-- testsupport/wait/awaitility.go | 26 ++++++------ testsupport/wait/host.go | 23 +++++++---- 6 files changed, 137 insertions(+), 40 deletions(-) diff --git a/.govulncheck.yaml b/.govulncheck.yaml index fb027e09c..d4a858352 100644 --- a/.govulncheck.yaml +++ b/.govulncheck.yaml @@ -1 +1,14 @@ -ignored-vulnerabilities: [] +ignored-vulnerabilities: + # Incorrect parsing of IPv6 host literals in net/url + # Found in: net/url@go1.24.13 + # Fixed in: net/url@go1.25.8 + - id: GO-2026-4601 + info: https://pkg.go.dev/vuln/GO-2026-4601 + silence-until: 2026-04-23 + # FileInfo can escape from a Root in os + # Found in: os@go1.24.13 + # Fixed in: os@go1.25.8 + - id: GO-2026-4602 + info: https://pkg.go.dev/vuln/GO-2026-4602 + silence-until: 2026-04-23 + diff --git a/test/metrics/metrics_test.go b/test/metrics/metrics_test.go index 1258d258d..397328bea 100644 --- a/test/metrics/metrics_test.go +++ b/test/metrics/metrics_test.go @@ -32,18 +32,41 @@ func TestOperatorVersionMetrics(t *testing.T) { awaitilities := WaitForDeployments(t) t.Run("host-operator", func(t *testing.T) { + // given hostAwait := awaitilities.Host() - // host metrics should be available at this point - hostAwait.InitMetrics(t, awaitilities.Member1().ClusterName, awaitilities.Member2().ClusterName) + _, err := hostAwait.WaitForRouteToBeAvailable(t, hostAwait.Namespace, "host-operator-metrics-service", "/metrics") + require.NoError(t, err) - // when - labels := hostAwait.GetMetricLabels(t, wait.HostOperatorVersionMetric) + t.Run("commit", func(t *testing.T) { + // when + labels := hostAwait.GetMetricLabels(t, hostAwait.MetricsURL, wait.HostOperatorCommitMetric) - // verify that the "version" metric exists for Host Operator and that it has a non-empty `commit` label - require.Len(t, labels, 1) - commit := labels[0]["commit"] - assert.Len(t, commit, 7) + // verify that the "version" metric exists for Host Operator and that it has a non-empty `commit` label + require.Len(t, labels, 1) + commit := labels[0]["commit"] + assert.Len(t, commit, len("e6a12a442a60dfd86d348a030ad2e789c79184b5")) // example value: 40 characters + }) + + t.Run("short commit", func(t *testing.T) { + // when + labels := hostAwait.GetMetricLabels(t, hostAwait.MetricsURL, wait.HostOperatorShortCommitMetric) + + // verify that the "version" metric exists for Host Operator and that it has a non-empty `commit` label + require.Len(t, labels, 1) + commit := labels[0]["commit"] + assert.Len(t, commit, 7) + }) + + t.Run("version", func(t *testing.T) { + // when + labels := hostAwait.GetMetricLabels(t, hostAwait.MetricsURL, wait.HostOperatorVersionMetric) + + // verify that the "version" metric exists for Host Operator and that it has a non-empty `commit` label + require.Len(t, labels, 1) + commit := labels[0]["commit"] + assert.Len(t, commit, 7) + }) }) t.Run("member-operators", func(t *testing.T) { @@ -56,7 +79,7 @@ func TestOperatorVersionMetrics(t *testing.T) { // --- member1 --- // when - labels := member1Await.GetMetricLabels(t, wait.MemberOperatorVersionMetric) + labels := member1Await.GetMetricLabels(t, member1Await.MetricsURL, wait.MemberOperatorVersionMetric) // verify that the "version" metric exists for the first Member Operator and that it has a non-empty `commit` label require.Len(t, labels, 1) @@ -65,7 +88,7 @@ func TestOperatorVersionMetrics(t *testing.T) { // --- member2 --- // when - labels = member2Await.GetMetricLabels(t, wait.MemberOperatorVersionMetric) + labels = member2Await.GetMetricLabels(t, member2Await.MetricsURL, wait.MemberOperatorVersionMetric) // verify that the "version" metric exists for the second Member Operator and that it has a non-empty `commit` label require.Len(t, labels, 1) @@ -75,6 +98,35 @@ func TestOperatorVersionMetrics(t *testing.T) { // expect the same version on member1 and member2 assert.Equal(t, commit1, commit2) }) + + t.Run("registration-service", func(t *testing.T) { + + // given + hostAwait := awaitilities.Host() + _, err := hostAwait.WaitForRouteToBeAvailable(t, hostAwait.Namespace, "host-operator-metrics-service", "/metrics") + require.NoError(t, err) + + t.Run("commit", func(t *testing.T) { + // when + labels := hostAwait.GetMetricLabels(t, hostAwait.RegistrationServiceMetricsURL, wait.RegistrationServiceCommitMetric) + + // verify that the "version" metric exists for Host Operator and that it has a non-empty `commit` label + require.Len(t, labels, 1) + commit := labels[0]["commit"] + assert.Len(t, commit, len("da3f54634cc65075d51d067a157831d44bf1413e")) // example value: 40 characters + }) + + t.Run("short commit", func(t *testing.T) { + // when + labels := hostAwait.GetMetricLabels(t, hostAwait.RegistrationServiceMetricsURL, wait.RegistrationServiceShortCommitMetric) + + // verify that the "version" metric exists for Host Operator and that it has a non-empty `commit` label + require.Len(t, labels, 1) + commit := labels[0]["commit"] + assert.Len(t, commit, 7) + }) + }) + } // TestMetricsWhenUsersManuallyApproved verifies that `UserSignupsApprovedMetric` and `UserSignupsApprovedWithMethodMetric` counters are increased when users are approved diff --git a/testsupport/init.go b/testsupport/init.go index fdd75d5d2..34b16ef2b 100644 --- a/testsupport/init.go +++ b/testsupport/init.go @@ -34,6 +34,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd/api" @@ -205,14 +206,43 @@ func WaitForDeployments(t *testing.T) wait.Awaitilities { require.NoError(t, err, "failed to find proxy metrics service") // setup host metrics route for metrics verification in tests - hostMetricsRoute, err := initHostAwait.SetupRouteForService(t, "host-operator-metrics-service", "/metrics") + hostMetricsRoute, err := initHostAwait.SetupRouteForService(t, "host-operator-metrics-service", "/metrics", + &routev1.RoutePort{ + TargetPort: intstr.FromString("https"), + }, + &routev1.TLSConfig{ + Termination: routev1.TLSTerminationPassthrough, + }, + ) require.NoError(t, err) initHostAwait.MetricsURL = "https://" + hostMetricsRoute.Status.Ingress[0].Host + t.Logf("host metrics URL: %s", initHostAwait.MetricsURL) + + // setup registration service metrics route for metrics verification in tests + registrationServiceMetricsRoute, err := initHostAwait.SetupRouteForService(t, "registration-service-metrics", "/metrics", + &routev1.RoutePort{ + TargetPort: intstr.FromString("regsvc-metrics"), + }, + &routev1.TLSConfig{ + Termination: routev1.TLSTerminationEdge, + }, + ) + require.NoError(t, err) + initHostAwait.RegistrationServiceMetricsURL = "https://" + registrationServiceMetricsRoute.Status.Ingress[0].Host + t.Logf("registration service metrics URL: %s", initHostAwait.RegistrationServiceMetricsURL) // setup member metrics route for metrics verification in tests - memberMetricsRoute, err := initMemberAwait.SetupRouteForService(t, "member-operator-metrics-service", "/metrics") + memberMetricsRoute, err := initMemberAwait.SetupRouteForService(t, "member-operator-metrics-service", "/metrics", + &routev1.RoutePort{ + TargetPort: intstr.FromString("https"), + }, + &routev1.TLSConfig{ + Termination: routev1.TLSTerminationPassthrough, + }, + ) require.NoError(t, err, "failed while setting up or waiting for the route to the 'member-operator-metrics' service to be available") initMemberAwait.MetricsURL = "https://" + memberMetricsRoute.Status.Ingress[0].Host + t.Logf("member metrics URL: %s", initMemberAwait.MetricsURL) // Wait for the webhooks in Member 1 only because we do not deploy webhooks for Member 2 // (we can't deploy the same webhook multiple times on the same cluster) diff --git a/testsupport/metrics/metrics.go b/testsupport/metrics/metrics.go index 312f2c043..d5fcffeb2 100644 --- a/testsupport/metrics/metrics.go +++ b/testsupport/metrics/metrics.go @@ -129,16 +129,14 @@ func getBuckets(t dto.MetricType, m *dto.Metric) (*[]*dto.Bucket, error) { // GetMetricLabels return all labels (indexed by key) for all metrics of the given `family` func GetMetricLabels(restConfig *rest.Config, baseURL string, family string) ([]map[string]string, error) { - uri := baseURL + "/metrics" - var metrics []byte - client := http.Client{ Timeout: time.Duration(30 * time.Second), Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec }, } - request, err := http.NewRequest("Get", uri, nil) + uri := baseURL + "/metrics" + request, err := http.NewRequest("GET", uri, nil) if err != nil { return nil, err } @@ -150,6 +148,7 @@ func GetMetricLabels(restConfig *rest.Config, baseURL string, family string) ([] defer func() { _ = resp.Body.Close() }() + var metrics []byte metrics, err = io.ReadAll(resp.Body) if err != nil { return nil, err diff --git a/testsupport/wait/awaitility.go b/testsupport/wait/awaitility.go index 1f3a59850..54f431430 100644 --- a/testsupport/wait/awaitility.go +++ b/testsupport/wait/awaitility.go @@ -32,7 +32,6 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/rest" "k8s.io/kubectl/pkg/util/podutils" @@ -210,8 +209,8 @@ func (a *Awaitility) GetToolchainCluster(t *testing.T, namespace string, cdtype // SetupRouteForService if needed, creates a route for the given service (with the same namespace/name) // It waits until the route is available (or returns an error) by first checking the resource status // and then making a call to the given endpoint -func (a *Awaitility) SetupRouteForService(t *testing.T, serviceName, endpoint string) (routev1.Route, error) { - t.Logf("setting up route for service '%s' with endpoint '%s'", serviceName, endpoint) +func (a *Awaitility) SetupRouteForService(t *testing.T, serviceName string, path string, port *routev1.RoutePort, tlsConfig *routev1.TLSConfig) (routev1.Route, error) { + t.Logf("setting up route for service '%s' with endpoint '%s'", serviceName, path) service, err := a.WaitForService(t, serviceName) if err != nil { return routev1.Route{}, err @@ -230,28 +229,24 @@ func (a *Awaitility) SetupRouteForService(t *testing.T, serviceName, endpoint st Name: service.Name, }, Spec: routev1.RouteSpec{ - Port: &routev1.RoutePort{ - TargetPort: intstr.FromString("https"), - }, - TLS: &routev1.TLSConfig{ - Termination: routev1.TLSTerminationPassthrough, - }, To: routev1.RouteTargetReference{ Kind: service.Kind, Name: service.Name, }, + Port: port, + TLS: tlsConfig, }, } if err = a.Client.Create(context.TODO(), &route); err != nil { return route, err } } - return a.WaitForRouteToBeAvailable(t, route.Namespace, route.Name, endpoint) + return a.WaitForRouteToBeAvailable(t, route.Namespace, route.Name, path) } // WaitForRouteToBeAvailable waits until the given route is available, ie, it has an Ingress with a host configured // and the endpoint is reachable (with a `200 OK` status response) -func (a *Awaitility) WaitForRouteToBeAvailable(t *testing.T, ns, name, endpoint string) (routev1.Route, error) { +func (a *Awaitility) WaitForRouteToBeAvailable(t *testing.T, ns, name, path string) (routev1.Route, error) { t.Logf("waiting for route '%s' in namespace '%s'", name, ns) route := routev1.Route{} // retrieve the route for the registration service @@ -282,13 +277,13 @@ func (a *Awaitility) WaitForRouteToBeAvailable(t *testing.T, ns, name, endpoint InsecureSkipVerify: true, // nolint:gosec }, } - request, err = http.NewRequest("GET", "https://"+route.Status.Ingress[0].Host+endpoint, nil) + request, err = http.NewRequest("GET", "https://"+route.Status.Ingress[0].Host+path, nil) if err != nil { return false, err } request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", a.RestConfig.BearerToken)) } else { - request, err = http.NewRequest("GET", "http://"+route.Status.Ingress[0].Host+endpoint, nil) + request, err = http.NewRequest("GET", "http://"+route.Status.Ingress[0].Host+path, nil) if err != nil { return false, err } @@ -335,8 +330,9 @@ func (a *Awaitility) GetHistogramValues(t *testing.T, family string, labelAndVal // GetMetricValue gets the value of the metric with the given family and label key-value pair // fails if the metric with the given labelAndValues does not exist -func (a *Awaitility) GetMetricLabels(t *testing.T, family string) []map[string]string { - labels, err := metrics.GetMetricLabels(a.RestConfig, a.MetricsURL, family) +func (a *Awaitility) GetMetricLabels(t *testing.T, metricsURL, family string) []map[string]string { + t.Logf("getting labels for metric '%s' from '%s'", family, metricsURL) + labels, err := metrics.GetMetricLabels(a.RestConfig, metricsURL, family) require.NoError(t, err) return labels } diff --git a/testsupport/wait/host.go b/testsupport/wait/host.go index 53f9df008..6d51bcf6e 100644 --- a/testsupport/wait/host.go +++ b/testsupport/wait/host.go @@ -58,9 +58,10 @@ var ( // HostAwaitility the Awaitility for the Host cluster type HostAwaitility struct { *Awaitility - RegistrationServiceNs string - RegistrationServiceURL string - APIProxyURL string + RegistrationServiceNs string + RegistrationServiceURL string + RegistrationServiceMetricsURL string + APIProxyURL string } // NewHostAwaitility initializes a HostAwaitility @@ -81,10 +82,11 @@ func NewHostAwaitility(cfg *rest.Config, cl client.Client, ns string, registrati // WithRetryOptions returns a new HostAwaitility with the given RetryOptions applied func (a *HostAwaitility) WithRetryOptions(options ...RetryOption) *HostAwaitility { return &HostAwaitility{ - Awaitility: a.Awaitility.WithRetryOptions(options...), - RegistrationServiceNs: a.RegistrationServiceNs, - RegistrationServiceURL: a.RegistrationServiceURL, - APIProxyURL: a.APIProxyURL, + Awaitility: a.Awaitility.WithRetryOptions(options...), + RegistrationServiceNs: a.RegistrationServiceNs, + RegistrationServiceURL: a.RegistrationServiceURL, + RegistrationServiceMetricsURL: a.RegistrationServiceMetricsURL, + APIProxyURL: a.APIProxyURL, } } @@ -111,7 +113,12 @@ const ( UsersPerActivationsAndDomainMetric = "sandbox_users_per_activations_and_domain" - HostOperatorVersionMetric = "sandbox_host_operator_version" + HostOperatorVersionMetric = "sandbox_host_operator_version" // DEPRECATED: use HostOperatorCommitMetric instead + HostOperatorCommitMetric = "sandbox_host_operator_commit" + HostOperatorShortCommitMetric = "sandbox_host_operator_short_commit" + + RegistrationServiceCommitMetric = "sandbox_registration_service_commit" + RegistrationServiceShortCommitMetric = "sandbox_registration_service_short_commit" SignupProvisionTimeMetric = "sandbox_user_signup_provision_time" )