mirror of
https://github.com/grafana/grafana.git
synced 2024-11-28 19:54:10 -06:00
Elasticsearch: Implement CheckHealth method in the backend (#81671)
* Elasticsearch: Implement CheckHealth method
* improve logger output
* remove frontend healthcheck
* Revert "remove frontend healthcheck"
This reverts commit 676265f39e
.
* adapt test
---------
Co-authored-by: Sven Grossmann <svennergr@gmail.com>
This commit is contained in:
parent
6f02d193f6
commit
65e9990a87
107
pkg/tsdb/elasticsearch/healthcheck.go
Normal file
107
pkg/tsdb/elasticsearch/healthcheck.go
Normal file
@ -0,0 +1,107 @@
|
||||
package elasticsearch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
)
|
||||
|
||||
func (s *Service) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
||||
logger := eslog.FromContext(ctx)
|
||||
|
||||
ds, err := s.getDSInfo(ctx, req.PluginContext)
|
||||
if err != nil {
|
||||
logger.Error("Failed to get data source info", "error", err)
|
||||
return &backend.CheckHealthResult{
|
||||
Status: backend.HealthStatusUnknown,
|
||||
Message: "Failed to get data source info",
|
||||
}, err
|
||||
}
|
||||
|
||||
esUrl, err := url.Parse(ds.URL)
|
||||
if err != nil {
|
||||
logger.Error("Failed to parse data source URL", "error", err, "url", ds.URL)
|
||||
return &backend.CheckHealthResult{
|
||||
Status: backend.HealthStatusUnknown,
|
||||
Message: "Failed to parse data source URL",
|
||||
}, err
|
||||
}
|
||||
|
||||
esUrl.Path = path.Join(esUrl.Path, "_cluster/health")
|
||||
esUrl.RawQuery = "wait_for_status=yellow"
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, "GET", esUrl.String(), nil)
|
||||
if err != nil {
|
||||
logger.Error("Failed to create request", "error", err, "url", esUrl.String())
|
||||
return &backend.CheckHealthResult{
|
||||
Status: backend.HealthStatusUnknown,
|
||||
Message: "Failed to create request",
|
||||
}, err
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
logger.Debug("Sending healthcheck request to Elasticsearch", "url", esUrl.String())
|
||||
response, err := ds.HTTPClient.Do(request)
|
||||
|
||||
if err != nil {
|
||||
logger.Error("Failed to do healthcheck request", "error", err, "url", esUrl.String())
|
||||
return &backend.CheckHealthResult{
|
||||
Status: backend.HealthStatusUnknown,
|
||||
Message: "Failed to do healthcheck request",
|
||||
}, err
|
||||
}
|
||||
|
||||
if response.StatusCode == http.StatusRequestTimeout {
|
||||
return &backend.CheckHealthResult{
|
||||
Status: backend.HealthStatusError,
|
||||
Message: "Elasticsearch data source is not healthy",
|
||||
}, nil
|
||||
}
|
||||
|
||||
logger.Info("Response received from Elasticsearch", "statusCode", response.StatusCode, "status", "ok", "duration", time.Since(start))
|
||||
|
||||
defer func() {
|
||||
if err := response.Body.Close(); err != nil {
|
||||
logger.Warn("Failed to close response body", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
body, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
logger.Error("Error reading response body bytes", "error", err)
|
||||
return &backend.CheckHealthResult{
|
||||
Status: backend.HealthStatusUnknown,
|
||||
Message: "Failed to read response",
|
||||
}, err
|
||||
}
|
||||
|
||||
jsonData := map[string]any{}
|
||||
|
||||
err = json.Unmarshal(body, &jsonData)
|
||||
if err != nil {
|
||||
logger.Error("Error during json unmarshal of the body", "error", err)
|
||||
return &backend.CheckHealthResult{
|
||||
Status: backend.HealthStatusUnknown,
|
||||
Message: "Failed to unmarshal response",
|
||||
}, err
|
||||
}
|
||||
|
||||
status := backend.HealthStatusOk
|
||||
message := "Elasticsearch data source is healthy"
|
||||
|
||||
if jsonData["status"] == "red" {
|
||||
status = backend.HealthStatusError
|
||||
message = "Elasticsearch data source is not healthy"
|
||||
}
|
||||
|
||||
return &backend.CheckHealthResult{
|
||||
Status: status,
|
||||
Message: message,
|
||||
}, nil
|
||||
}
|
80
pkg/tsdb/elasticsearch/healthcheck_test.go
Normal file
80
pkg/tsdb/elasticsearch/healthcheck_test.go
Normal file
@ -0,0 +1,80 @@
|
||||
package elasticsearch
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
|
||||
es "github.com/grafana/grafana/pkg/tsdb/elasticsearch/client"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_Healthcheck_OK(t *testing.T) {
|
||||
service := GetMockService(true)
|
||||
res, _ := service.CheckHealth(context.Background(), &backend.CheckHealthRequest{
|
||||
PluginContext: backend.PluginContext{},
|
||||
Headers: nil,
|
||||
})
|
||||
assert.Equal(t, backend.HealthStatusOk, res.Status)
|
||||
assert.Equal(t, "Elasticsearch data source is healthy", res.Message)
|
||||
}
|
||||
|
||||
func Test_Healthcheck_Timeout(t *testing.T) {
|
||||
service := GetMockService(false)
|
||||
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", res.Message)
|
||||
}
|
||||
|
||||
type FakeRoundTripper struct {
|
||||
isDsHealthy bool
|
||||
}
|
||||
|
||||
func (fakeRoundTripper *FakeRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
var res *http.Response
|
||||
if fakeRoundTripper.isDsHealthy {
|
||||
res = &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Status: "200 OK",
|
||||
Body: io.NopCloser(bytes.NewBufferString("{\"status\":\"green\"}")),
|
||||
}
|
||||
} else {
|
||||
res = &http.Response{
|
||||
StatusCode: http.StatusRequestTimeout,
|
||||
Status: "408 Request Timeout",
|
||||
Body: io.NopCloser(bytes.NewBufferString("{\"status\":\"red\"}")),
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
type FakeInstanceManager struct {
|
||||
isDsHealthy bool
|
||||
}
|
||||
|
||||
func (fakeInstanceManager *FakeInstanceManager) Get(tx context.Context, pluginContext backend.PluginContext) (instancemgmt.Instance, error) {
|
||||
httpClient, _ := sdkhttpclient.New(sdkhttpclient.Options{})
|
||||
httpClient.Transport = &FakeRoundTripper{isDsHealthy: fakeInstanceManager.isDsHealthy}
|
||||
|
||||
return es.DatasourceInfo{
|
||||
HTTPClient: httpClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (*FakeInstanceManager) Do(_ context.Context, _ backend.PluginContext, _ instancemgmt.InstanceCallbackFunc) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetMockService(isDsHealthy bool) *Service {
|
||||
return &Service{
|
||||
im: &FakeInstanceManager{isDsHealthy: isDsHealthy},
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user