Elasticsearch: Health endpoint should handle http errors (#96803)

Elasticsearch: health endpoint should handle http errors
This commit is contained in:
Isabella Siu 2024-12-03 10:37:31 -05:00 committed by GitHub
parent 5c2cda7abb
commit 91bdf733fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 35 additions and 10 deletions

View File

@ -3,6 +3,7 @@ package elasticsearch
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
@ -10,6 +11,7 @@ import (
"time" "time"
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/experimental/errorsource"
) )
func (s *Service) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { func (s *Service) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
@ -51,6 +53,9 @@ func (s *Service) CheckHealth(ctx context.Context, req *backend.CheckHealthReque
if err != nil { if err != nil {
logger.Error("Failed to do healthcheck request", "error", err, "url", esUrl.String()) logger.Error("Failed to do healthcheck request", "error", err, "url", esUrl.String())
if backend.IsDownstreamHTTPError(err) {
err = errorsource.DownstreamError(err, false)
}
return &backend.CheckHealthResult{ return &backend.CheckHealthResult{
Status: backend.HealthStatusUnknown, Status: backend.HealthStatusUnknown,
Message: "Failed to do healthcheck request", Message: "Failed to do healthcheck request",
@ -64,6 +69,14 @@ func (s *Service) CheckHealth(ctx context.Context, req *backend.CheckHealthReque
}, nil }, nil
} }
if response.StatusCode >= 400 {
errWithSource := errorsource.SourceError(backend.ErrorSourceFromHTTPStatus(response.StatusCode), fmt.Errorf("unexpected status code: %d", response.StatusCode), false)
return &backend.CheckHealthResult{
Status: backend.HealthStatusError,
Message: fmt.Sprintf("Elasticsearch data source is not healthy. Status: %s", response.Status),
}, errWithSource
}
logger.Info("Response received from Elasticsearch", "statusCode", response.StatusCode, "status", "ok", "duration", time.Since(start)) logger.Info("Response received from Elasticsearch", "statusCode", response.StatusCode, "status", "ok", "duration", time.Since(start))
defer func() { defer func() {

View File

@ -16,7 +16,7 @@ import (
) )
func Test_Healthcheck_OK(t *testing.T) { func Test_Healthcheck_OK(t *testing.T) {
service := GetMockService(true) service := GetMockService(http.StatusOK, "200 OK")
res, _ := service.CheckHealth(context.Background(), &backend.CheckHealthRequest{ res, _ := service.CheckHealth(context.Background(), &backend.CheckHealthRequest{
PluginContext: backend.PluginContext{}, PluginContext: backend.PluginContext{},
Headers: nil, Headers: nil,
@ -26,7 +26,7 @@ func Test_Healthcheck_OK(t *testing.T) {
} }
func Test_Healthcheck_Timeout(t *testing.T) { func Test_Healthcheck_Timeout(t *testing.T) {
service := GetMockService(false) service := GetMockService(http.StatusRequestTimeout, "408 Request Timeout")
res, _ := service.CheckHealth(context.Background(), &backend.CheckHealthRequest{ res, _ := service.CheckHealth(context.Background(), &backend.CheckHealthRequest{
PluginContext: backend.PluginContext{}, PluginContext: backend.PluginContext{},
Headers: nil, Headers: nil,
@ -35,13 +35,24 @@ func Test_Healthcheck_Timeout(t *testing.T) {
assert.Equal(t, "Elasticsearch data source is not healthy", res.Message) assert.Equal(t, "Elasticsearch data source is not healthy", res.Message)
} }
func Test_Healthcheck_Error(t *testing.T) {
service := GetMockService(http.StatusBadGateway, "502 Bad Gateway")
res, _ := service.CheckHealth(context.Background(), &backend.CheckHealthRequest{
PluginContext: backend.PluginContext{},
Headers: nil,
})
assert.Equal(t, backend.HealthStatusError, res.Status)
assert.Equal(t, "Elasticsearch data source is not healthy. Status: 502 Bad Gateway", res.Message)
}
type FakeRoundTripper struct { type FakeRoundTripper struct {
isDsHealthy bool statusCode int
status string
} }
func (fakeRoundTripper *FakeRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { func (fakeRoundTripper *FakeRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
var res *http.Response var res *http.Response
if fakeRoundTripper.isDsHealthy { if fakeRoundTripper.statusCode == http.StatusOK {
res = &http.Response{ res = &http.Response{
StatusCode: http.StatusOK, StatusCode: http.StatusOK,
Status: "200 OK", Status: "200 OK",
@ -49,8 +60,8 @@ func (fakeRoundTripper *FakeRoundTripper) RoundTrip(req *http.Request) (*http.Re
} }
} else { } else {
res = &http.Response{ res = &http.Response{
StatusCode: http.StatusRequestTimeout, StatusCode: fakeRoundTripper.statusCode,
Status: "408 Request Timeout", Status: fakeRoundTripper.status,
Body: io.NopCloser(bytes.NewBufferString("{\"status\":\"red\"}")), Body: io.NopCloser(bytes.NewBufferString("{\"status\":\"red\"}")),
} }
} }
@ -58,12 +69,13 @@ func (fakeRoundTripper *FakeRoundTripper) RoundTrip(req *http.Request) (*http.Re
} }
type FakeInstanceManager struct { type FakeInstanceManager struct {
isDsHealthy bool statusCode int
status string
} }
func (fakeInstanceManager *FakeInstanceManager) Get(tx context.Context, pluginContext backend.PluginContext) (instancemgmt.Instance, error) { func (fakeInstanceManager *FakeInstanceManager) Get(tx context.Context, pluginContext backend.PluginContext) (instancemgmt.Instance, error) {
httpClient, _ := httpclient.New(httpclient.Options{}) httpClient, _ := httpclient.New(httpclient.Options{})
httpClient.Transport = &FakeRoundTripper{isDsHealthy: fakeInstanceManager.isDsHealthy} httpClient.Transport = &FakeRoundTripper{statusCode: fakeInstanceManager.statusCode, status: fakeInstanceManager.status}
return es.DatasourceInfo{ return es.DatasourceInfo{
HTTPClient: httpClient, HTTPClient: httpClient,
@ -74,9 +86,9 @@ func (*FakeInstanceManager) Do(_ context.Context, _ backend.PluginContext, _ ins
return nil return nil
} }
func GetMockService(isDsHealthy bool) *Service { func GetMockService(statusCode int, status string) *Service {
return &Service{ return &Service{
im: &FakeInstanceManager{isDsHealthy: isDsHealthy}, im: &FakeInstanceManager{statusCode: statusCode, status: status},
logger: log.New(), logger: log.New(),
} }
} }