Instrumentation: Add status_source label to request metrics/logs (#74114)

Ref #68480

Co-authored-by: Giuseppe Guerra <giuseppe.guerra@grafana.com>
This commit is contained in:
Marcus Efraimsson
2023-09-11 12:13:13 +02:00
committed by GitHub
parent 97d568e60a
commit 8ee43f3705
27 changed files with 487 additions and 67 deletions

View File

@ -12,11 +12,13 @@ import (
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/stretchr/testify/require"
"golang.org/x/oauth2"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/middleware/requestmeta"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/plugins/log"
@ -262,6 +264,123 @@ func TestIntegrationBackendPlugins(t *testing.T) {
require.Equal(t, "msg 1\r\nmsg 2\r\n", string(bytes))
})
})
newTestScenario(t, "Query data error should return expected status code and marked with downstream status",
options(),
func(t *testing.T, tsCtx *testScenarioContext) {
tsCtx.backendTestPlugin.QueryDataHandler = backend.QueryDataHandlerFunc(func(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
return nil, fmt.Errorf("BOOM")
})
req := createQueryDataHTTPRequest(t, tsCtx, createRegularQuery(t, tsCtx))
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
require.NotNil(t, tsCtx.incomingRequest)
require.Equal(t, "/api/ds/query", tsCtx.incomingRequest.URL.Path)
rmd := requestmeta.GetRequestMetaData(tsCtx.incomingRequest.Context())
require.Equal(t, requestmeta.StatusSourceDownstream, rmd.StatusSource)
t.Cleanup(func() {
err := resp.Body.Close()
require.NoError(t, err)
})
})
newTestScenario(t, "Call resource error should return expected status code and marked with downstream status",
options(),
func(t *testing.T, tsCtx *testScenarioContext) {
tsCtx.backendTestPlugin.CallResourceHandler = backend.CallResourceHandlerFunc(func(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
return fmt.Errorf("BOOM")
})
req := createCallResourceHTTPRequest(t, tsCtx)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
require.NotNil(t, tsCtx.incomingRequest)
require.Equal(t, "/api/datasources/uid/test-plugin/resources", tsCtx.incomingRequest.URL.Path)
rmd := requestmeta.GetRequestMetaData(tsCtx.incomingRequest.Context())
require.Equal(t, requestmeta.StatusSourceDownstream, rmd.StatusSource)
t.Cleanup(func() {
err := resp.Body.Close()
require.NoError(t, err)
})
})
newTestScenario(t, "Check health error should return expected status code and marked with downstream status",
options(),
func(t *testing.T, tsCtx *testScenarioContext) {
tsCtx.backendTestPlugin.CheckHealthHandler = backend.CheckHealthHandlerFunc(func(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
return nil, fmt.Errorf("BOOM")
})
req := createCheckHealthHTTPRequest(t, tsCtx)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
require.NotNil(t, tsCtx.incomingRequest)
require.Equal(t, "/api/datasources/uid/test-plugin/health", tsCtx.incomingRequest.URL.Path)
rmd := requestmeta.GetRequestMetaData(tsCtx.incomingRequest.Context())
require.Equal(t, requestmeta.StatusSourceDownstream, rmd.StatusSource)
t.Cleanup(func() {
err := resp.Body.Close()
require.NoError(t, err)
})
})
newTestScenario(t, "Call resource response with 502 status code should be marked with downstream status",
options(),
func(t *testing.T, tsCtx *testScenarioContext) {
tsCtx.backendTestPlugin.CallResourceHandler = backend.CallResourceHandlerFunc(func(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
return sender.Send(&backend.CallResourceResponse{
Status: http.StatusBadGateway,
Headers: map[string][]string{},
})
})
req := createCallResourceHTTPRequest(t, tsCtx)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
b, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, http.StatusBadGateway, resp.StatusCode, string(b))
require.NotNil(t, tsCtx.incomingRequest)
require.Equal(t, "/api/datasources/uid/test-plugin/resources", tsCtx.incomingRequest.URL.Path)
rmd := requestmeta.GetRequestMetaData(tsCtx.incomingRequest.Context())
require.Equal(t, requestmeta.StatusSourceDownstream, rmd.StatusSource)
t.Cleanup(func() {
err := resp.Body.Close()
require.NoError(t, err)
})
})
newTestScenario(t, "Query data response that includes a query data response error should return expected status code and marked with downstream status",
options(),
func(t *testing.T, tsCtx *testScenarioContext) {
tsCtx.backendTestPlugin.QueryDataHandler = backend.QueryDataHandlerFunc(func(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
resp := backend.NewQueryDataResponse()
resp.Responses["A"] = backend.DataResponse{
Frames: data.Frames{},
}
resp.Responses["B"] = backend.DataResponse{
Error: fmt.Errorf("BOOM"),
}
return resp, nil
})
req := createQueryDataHTTPRequest(t, tsCtx, createRegularQuery(t, tsCtx))
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
require.NotNil(t, tsCtx.incomingRequest)
require.Equal(t, "/api/ds/query", tsCtx.incomingRequest.URL.Path)
rmd := requestmeta.GetRequestMetaData(tsCtx.incomingRequest.Context())
require.Equal(t, requestmeta.StatusSourceDownstream, rmd.StatusSource)
t.Cleanup(func() {
err := resp.Body.Close()
require.NoError(t, err)
})
})
}
type testScenarioContext struct {
@ -275,6 +394,7 @@ type testScenarioContext struct {
rt http.RoundTripper
modifyIncomingRequest func(req *http.Request)
modifyCallResourceResponse func(sender backend.CallResourceResponseSender) error
incomingRequest *http.Request
}
type testScenarioInput struct {
@ -350,6 +470,12 @@ func newTestScenario(t *testing.T, name string, opts []testScenarioOption, callb
})
grafanaListeningAddr, testEnv := testinfra.StartGrafanaEnv(t, dir, path)
testEnv.RequestMiddleware = func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tsCtx.incomingRequest = r
next.ServeHTTP(w, r)
})
}
tsCtx.grafanaListeningAddr = grafanaListeningAddr
testEnv.SQLStore.Cfg.LoginCookieName = loginCookieName
tsCtx.testEnv = testEnv
@ -495,20 +621,13 @@ func (tsCtx *testScenarioContext) runQueryDataTest(t *testing.T, mr dtos.MetricR
return &backend.QueryDataResponse{}, nil
})
buf1 := &bytes.Buffer{}
err := json.NewEncoder(buf1).Encode(mr)
require.NoError(t, err)
u := fmt.Sprintf("http://admin:admin@%s/api/ds/query", tsCtx.grafanaListeningAddr)
req, err := http.NewRequest(http.MethodPost, u, buf1)
req.Header.Set("Content-Type", "application/json")
req := createQueryDataHTTPRequest(t, tsCtx, mr)
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36")
if tsCtx.modifyIncomingRequest != nil {
tsCtx.modifyIncomingRequest(req)
}
require.NoError(t, err)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
b, err := io.ReadAll(resp.Body)
@ -559,9 +678,7 @@ func (tsCtx *testScenarioContext) runCheckHealthTest(t *testing.T, callback func
}, nil
})
u := fmt.Sprintf("http://admin:admin@%s/api/datasources/uid/%s/health", tsCtx.grafanaListeningAddr, tsCtx.uid)
req, err := http.NewRequest(http.MethodGet, u, nil)
req := createCheckHealthHTTPRequest(t, tsCtx)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36")
@ -569,7 +686,6 @@ func (tsCtx *testScenarioContext) runCheckHealthTest(t *testing.T, callback func
tsCtx.modifyIncomingRequest(req)
}
require.NoError(t, err)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
b, err := io.ReadAll(resp.Body)
@ -624,9 +740,7 @@ func (tsCtx *testScenarioContext) runCallResourceTest(t *testing.T, callback fun
return tsCtx.modifyCallResourceResponse(sender)
})
u := fmt.Sprintf("http://admin:admin@%s/api/datasources/uid/%s/resources", tsCtx.grafanaListeningAddr, tsCtx.uid)
req, err := http.NewRequest(http.MethodGet, u, nil)
req := createCallResourceHTTPRequest(t, tsCtx)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Connection", "X-Some-Conn-Header")
req.Header.Set("X-Some-Conn-Header", "should be deleted")
@ -637,7 +751,6 @@ func (tsCtx *testScenarioContext) runCallResourceTest(t *testing.T, callback fun
tsCtx.modifyIncomingRequest(req)
}
require.NoError(t, err)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
t.Cleanup(func() {
@ -667,6 +780,39 @@ func (tsCtx *testScenarioContext) runCallResourceTest(t *testing.T, callback fun
})
}
func createCheckHealthHTTPRequest(t *testing.T, tsCtx *testScenarioContext) *http.Request {
t.Helper()
u := fmt.Sprintf("http://admin:admin@%s/api/datasources/uid/%s/health", tsCtx.grafanaListeningAddr, tsCtx.uid)
req, err := http.NewRequest(http.MethodGet, u, nil)
require.NoError(t, err)
return req
}
func createCallResourceHTTPRequest(t *testing.T, tsCtx *testScenarioContext) *http.Request {
t.Helper()
u := fmt.Sprintf("http://admin:admin@%s/api/datasources/uid/%s/resources", tsCtx.grafanaListeningAddr, tsCtx.uid)
req, err := http.NewRequest(http.MethodGet, u, nil)
require.NoError(t, err)
return req
}
func createQueryDataHTTPRequest(t *testing.T, tsCtx *testScenarioContext, mr dtos.MetricRequest) *http.Request {
t.Helper()
buf1 := &bytes.Buffer{}
err := json.NewEncoder(buf1).Encode(mr)
require.NoError(t, err)
u := fmt.Sprintf("http://admin:admin@%s/api/ds/query", tsCtx.grafanaListeningAddr)
req, err := http.NewRequest(http.MethodPost, u, buf1)
require.NoError(t, err)
req.Header.Set("Content-Type", "application/json")
return req
}
func createTestPlugin(id string) (*plugins.Plugin, *testPlugin) {
p := &plugins.Plugin{
JSONData: plugins.JSONData{