diff --git a/test/extended/prometheus/prometheus.go b/test/extended/prometheus/prometheus.go index de83aeb55af9..5208ceee2e7a 100644 --- a/test/extended/prometheus/prometheus.go +++ b/test/extended/prometheus/prometheus.go @@ -5,9 +5,7 @@ import ( "context" "encoding/json" "fmt" - "os/exec" "regexp" - "strconv" "strings" "time" @@ -367,7 +365,7 @@ var _ = g.Describe("[sig-instrumentation] Prometheus [apigroup:image.openshift.i g.By("checking the prometheus metrics path") var metrics map[string]*dto.MetricFamily - o.Expect(wait.PollImmediate(10*time.Second, 2*time.Minute, func() (bool, error) { + o.Expect(wait.PollUntilContextTimeout(context.Background(), 10*time.Second, 2*time.Minute, true, func(context.Context) (bool, error) { results, err := getBearerTokenURLViaPod(ns, execPod.Name, fmt.Sprintf("%s/metrics", prometheusSvcURL), bearerToken) if err != nil { e2e.Logf("unable to get metrics: %v", err) @@ -400,14 +398,16 @@ var _ = g.Describe("[sig-instrumentation] Prometheus [apigroup:image.openshift.i o.Expect(err).NotTo(o.HaveOccurred()) g.By("verifying a service account token is able to authenticate") - err = expectBearerTokenURLStatusCodeExec(fmt.Sprintf("%s/api/v1/targets", queryURL), bearerToken, 200) + + err = helper.ExpectURLStatusCodeExec(helper.MustJoinUrlPath(queryURL, "api/v1/targets"), bearerToken, 200) + o.Expect(err).NotTo(o.HaveOccurred()) g.By("verifying a service account token is able to access the Prometheus API") // expect all endpoints within 60 seconds var lastErrs []error - o.Expect(wait.PollImmediate(10*time.Second, 2*time.Minute, func() (bool, error) { - contents, err := getBearerTokenURL(fmt.Sprintf("%s/api/v1/targets", prometheusURL), bearerToken) + o.Expect(wait.PollUntilContextTimeout(context.Background(), 10*time.Second, 2*time.Minute, true, func(context.Context) (bool, error) { + contents, err := helper.GetBearerTokenURL(helper.MustJoinUrlPath(prometheusURL, "api/v1/targets"), bearerToken) o.Expect(err).NotTo(o.HaveOccurred()) targets := &prometheusTargets{} @@ -456,7 +456,7 @@ var _ = g.Describe("[sig-instrumentation] Prometheus [apigroup:image.openshift.i g.By("verifying all targets are exposing metrics over secure channel") var insecureTargets []error - contents, err := getBearerTokenURL(fmt.Sprintf("%s/api/v1/targets", prometheusURL), bearerToken) + contents, err := helper.GetBearerTokenURL(helper.MustJoinUrlPath(prometheusURL, "api/v1/targets"), bearerToken) o.Expect(err).NotTo(o.HaveOccurred()) targets := &prometheusTargets{} @@ -579,8 +579,8 @@ var _ = g.Describe("[sig-instrumentation] Prometheus [apigroup:image.openshift.i g.It("should provide ingress metrics", func() { var lastErrs []error - o.Expect(wait.PollImmediate(10*time.Second, 4*time.Minute, func() (bool, error) { - contents, err := getBearerTokenURL(fmt.Sprintf("%s/api/v1/targets", prometheusURL), bearerToken) + o.Expect(wait.PollUntilContextTimeout(context.Background(), 10*time.Second, 4*time.Minute, true, func(ctx context.Context) (bool, error) { + contents, err := helper.GetBearerTokenURL(helper.MustJoinUrlPath(prometheusURL, "api/v1/targets"), bearerToken) o.Expect(err).NotTo(o.HaveOccurred()) targets := &prometheusTargets{} @@ -758,27 +758,6 @@ func findMetricLabels(f *dto.MetricFamily, labels map[string]string, match strin return result } -func expectBearerTokenURLStatusCodeExec(url, bearer string, statusCode int) error { - cmd := fmt.Sprintf("curl -k -s -H 'Authorization: Bearer %s' -o /dev/null -w '%%{http_code}' %q", bearer, url) - output, err := exec.Command("bash", "-e", "-c", cmd).Output() - if err != nil { - return fmt.Errorf("host command failed: %v\n%s", err, output) - } - if string(output) != strconv.Itoa(statusCode) { - return fmt.Errorf("last response from server was not %d: %s", statusCode, output) - } - return nil -} - -func getBearerTokenURL(url, bearer string) (string, error) { - cmd := fmt.Sprintf("curl -s -k -H 'Authorization: Bearer %s' %q", bearer, url) - output, err := exec.Command("bash", "-e", "-c", cmd).Output() - if err != nil { - return "", fmt.Errorf("host command failed: %v\n%s", err, output) - } - return string(output), nil -} - func getBearerTokenURLViaPod(ns, execPodName, url, bearer string) (string, error) { cmd := fmt.Sprintf("curl -s -k -H 'Authorization: Bearer %s' %q", bearer, url) output, err := e2eoutput.RunHostCmd(ns, execPodName, cmd) diff --git a/test/extended/util/prometheus/helpers.go b/test/extended/util/prometheus/helpers.go index 1bfa2fac6b46..be9c8478b2f3 100644 --- a/test/extended/util/prometheus/helpers.go +++ b/test/extended/util/prometheus/helpers.go @@ -2,11 +2,12 @@ package prometheus import ( "context" + "crypto/tls" "encoding/json" "fmt" + "io" "net/http" "net/url" - "os/exec" "strconv" "strings" "time" @@ -45,13 +46,51 @@ type prometheusResponseData struct { } // GetBearerTokenURL makes http request with bearer token -func GetBearerTokenURL(url, bearer string) (string, error) { - cmd := fmt.Sprintf("curl --retry 15 --max-time 2 --retry-delay 1 -s -k -H 'Authorization: Bearer %s' %q", bearer, url) - output, err := exec.Command("bash", "-e", "-c", cmd).Output() +func GetBearerTokenURL(url, bearerToken string) (string, error) { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client := &http.Client{ + Timeout: time.Duration(10 * time.Second), + Transport: tr, + } + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return "", err + } + + req.Header.Add("Authorization", "Bearer "+bearerToken) + + var ( + body []byte + lastErr error + ) + condition := func(ctx context.Context) (bool, error) { + resp, err := client.Do(req) + if err != nil { + return false, err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + lastErr = fmt.Errorf("%s: unexpected status code: %d", url, resp.StatusCode) + return false, err + } + + body, err = io.ReadAll(resp.Body) + if err != nil { + lastErr = fmt.Errorf("%s: failed to read response: %w", url, err) + return false, nil + } + + return true, nil + } + err = wait.PollUntilContextTimeout(context.Background(), 10*time.Second, 60*time.Second, true, condition) if err != nil { - return "", fmt.Errorf("host command failed: %v\n%s", err, output) + return "", fmt.Errorf("%w: %w", err, lastErr) } - return string(output), nil + + return string(body), nil } func waitForServiceAccountInNamespace(c clientset.Interface, ns, serviceAccountName string, timeout time.Duration) error { @@ -285,19 +324,34 @@ func RunQueries(ctx context.Context, prometheusClient prometheusv1.API, promQuer // ExpectURLStatusCodeExec attempts connection to url returning an error // upon failure or if status return code is not equal to any of the statusCodes. -func ExpectURLStatusCodeExec(url string, statusCodes ...int) error { - cmd := fmt.Sprintf("curl -k -s -o /dev/null -w '%%{http_code}' %q", url) - output, err := exec.Command("bash", "-e", "-c", cmd).Output() +func ExpectURLStatusCodeExec(url, bearerToken string, statusCodes ...int) error { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client := &http.Client{ + Transport: tr, + Timeout: time.Duration(10 * time.Second), + } + req, err := http.NewRequest("GET", url, nil) if err != nil { - return fmt.Errorf("host command failed: %v\n%s", err, output) + return err + } + if len(bearerToken) > 0 { + req.Header.Add("Authorization", "Bearer "+bearerToken) } + + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + for _, statusCode := range statusCodes { - if string(output) == strconv.Itoa(statusCode) { + if resp.StatusCode == statusCode { return nil } } - - return fmt.Errorf("last response from server was not in %v: %s", statusCodes, output) + return fmt.Errorf("%s: last response from server was not in %v: %d", url, statusCodes, resp.StatusCode) } // ExpectURLStatusCodeExecViaPod attempts connection to url via exec pod and returns an error @@ -314,7 +368,7 @@ func ExpectURLStatusCodeExecViaPod(ns, execPodName, url string, statusCodes ...i } } - return fmt.Errorf("last response from server was not in %v: %s", statusCodes, output) + return fmt.Errorf("%s: last response from server was not in %v: %s", url, statusCodes, output) } // ExpectPrometheusEndpoint attempts to connect to the metrics endpoint with @@ -322,7 +376,7 @@ func ExpectURLStatusCodeExecViaPod(ns, execPodName, url string, statusCodes ...i func ExpectPrometheusEndpoint(url string) { var err error for i := 0; i < maxPrometheusQueryAttempts; i++ { - err = ExpectURLStatusCodeExec(url, 401, 403) + err = ExpectURLStatusCodeExec(url, "", 401, 403) if err == nil { break } @@ -444,3 +498,12 @@ func ForEachAlertingRule(rules map[string][]promv1.AlertingRule, f func(a promv1 return fmt.Errorf("Incompliant rules detected:\n\n%s", strings.Join(allViolations.List(), "\n")) } + +// MustJoinUrlPath behaves like url.JoinPath but it will panic in case of error. +func MustJoinUrlPath(base string, paths ...string) string { + path, err := url.JoinPath(base, paths...) + if err != nil { + panic(err) + } + return path +}