mirror of https://github.com/grafana/grafana
Plugins: Fix status_source always being "plugin" in plugin request logs (#77433)
* Plugins: Fix status_source always being "plugin" in plugin logs * add tests * Fix TestInstrumentationMiddlewareStatusSourcepull/77439/head
parent
165a76a12b
commit
46261de32d
@ -0,0 +1,69 @@ |
|||||||
|
package clientmiddleware |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
|
||||||
|
"github.com/grafana/grafana-plugin-sdk-go/backend" |
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/plugins" |
||||||
|
"github.com/grafana/grafana/pkg/plugins/pluginrequestmeta" |
||||||
|
) |
||||||
|
|
||||||
|
// NewPluginRequestMetaMiddleware returns a new plugins.ClientMiddleware that sets up the default
|
||||||
|
// values for the plugin request meta in the context.Context. All middlewares that are executed
|
||||||
|
// after this one are be able to access plugin request meta via the pluginrequestmeta package.
|
||||||
|
func NewPluginRequestMetaMiddleware() plugins.ClientMiddleware { |
||||||
|
return plugins.ClientMiddlewareFunc(func(next plugins.Client) plugins.Client { |
||||||
|
return &PluginRequestMetaMiddleware{ |
||||||
|
next: next, |
||||||
|
defaultStatusSource: pluginrequestmeta.StatusSourcePlugin, |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
type PluginRequestMetaMiddleware struct { |
||||||
|
next plugins.Client |
||||||
|
defaultStatusSource pluginrequestmeta.StatusSource |
||||||
|
} |
||||||
|
|
||||||
|
func (m *PluginRequestMetaMiddleware) withDefaultPluginRequestMeta(ctx context.Context) context.Context { |
||||||
|
// Setup plugin request status source
|
||||||
|
ctx = pluginrequestmeta.WithStatusSource(ctx, m.defaultStatusSource) |
||||||
|
|
||||||
|
return ctx |
||||||
|
} |
||||||
|
|
||||||
|
func (m *PluginRequestMetaMiddleware) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { |
||||||
|
ctx = m.withDefaultPluginRequestMeta(ctx) |
||||||
|
return m.next.QueryData(ctx, req) |
||||||
|
} |
||||||
|
|
||||||
|
func (m *PluginRequestMetaMiddleware) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error { |
||||||
|
ctx = m.withDefaultPluginRequestMeta(ctx) |
||||||
|
return m.next.CallResource(ctx, req, sender) |
||||||
|
} |
||||||
|
|
||||||
|
func (m *PluginRequestMetaMiddleware) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { |
||||||
|
ctx = m.withDefaultPluginRequestMeta(ctx) |
||||||
|
return m.next.CheckHealth(ctx, req) |
||||||
|
} |
||||||
|
|
||||||
|
func (m *PluginRequestMetaMiddleware) CollectMetrics(ctx context.Context, req *backend.CollectMetricsRequest) (*backend.CollectMetricsResult, error) { |
||||||
|
ctx = m.withDefaultPluginRequestMeta(ctx) |
||||||
|
return m.next.CollectMetrics(ctx, req) |
||||||
|
} |
||||||
|
|
||||||
|
func (m *PluginRequestMetaMiddleware) SubscribeStream(ctx context.Context, req *backend.SubscribeStreamRequest) (*backend.SubscribeStreamResponse, error) { |
||||||
|
ctx = m.withDefaultPluginRequestMeta(ctx) |
||||||
|
return m.next.SubscribeStream(ctx, req) |
||||||
|
} |
||||||
|
|
||||||
|
func (m *PluginRequestMetaMiddleware) PublishStream(ctx context.Context, req *backend.PublishStreamRequest) (*backend.PublishStreamResponse, error) { |
||||||
|
ctx = m.withDefaultPluginRequestMeta(ctx) |
||||||
|
return m.next.PublishStream(ctx, req) |
||||||
|
} |
||||||
|
|
||||||
|
func (m *PluginRequestMetaMiddleware) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error { |
||||||
|
ctx = m.withDefaultPluginRequestMeta(ctx) |
||||||
|
return m.next.RunStream(ctx, req, sender) |
||||||
|
} |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
package clientmiddleware |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"testing" |
||||||
|
|
||||||
|
"github.com/grafana/grafana-plugin-sdk-go/backend" |
||||||
|
"github.com/stretchr/testify/require" |
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/plugins" |
||||||
|
"github.com/grafana/grafana/pkg/plugins/manager/client/clienttest" |
||||||
|
"github.com/grafana/grafana/pkg/plugins/pluginrequestmeta" |
||||||
|
) |
||||||
|
|
||||||
|
func TestPluginRequestMetaMiddleware(t *testing.T) { |
||||||
|
t.Run("default", func(t *testing.T) { |
||||||
|
cdt := clienttest.NewClientDecoratorTest(t, |
||||||
|
clienttest.WithMiddlewares(NewPluginRequestMetaMiddleware()), |
||||||
|
) |
||||||
|
_, err := cdt.Decorator.QueryData(context.Background(), &backend.QueryDataRequest{}) |
||||||
|
require.NoError(t, err) |
||||||
|
ss := pluginrequestmeta.StatusSourceFromContext(cdt.QueryDataCtx) |
||||||
|
require.Equal(t, pluginrequestmeta.StatusSourcePlugin, ss) |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("other value", func(t *testing.T) { |
||||||
|
cdt := clienttest.NewClientDecoratorTest(t, |
||||||
|
clienttest.WithMiddlewares(plugins.ClientMiddlewareFunc(func(next plugins.Client) plugins.Client { |
||||||
|
return &PluginRequestMetaMiddleware{ |
||||||
|
next: next, |
||||||
|
defaultStatusSource: "test", |
||||||
|
} |
||||||
|
})), |
||||||
|
) |
||||||
|
_, err := cdt.Decorator.QueryData(context.Background(), &backend.QueryDataRequest{}) |
||||||
|
require.NoError(t, err) |
||||||
|
ss := pluginrequestmeta.StatusSourceFromContext(cdt.QueryDataCtx) |
||||||
|
require.Equal(t, pluginrequestmeta.StatusSource("test"), ss) |
||||||
|
}) |
||||||
|
} |
||||||
@ -0,0 +1,83 @@ |
|||||||
|
package clientmiddleware |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"fmt" |
||||||
|
|
||||||
|
"github.com/grafana/grafana-plugin-sdk-go/backend" |
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/plugins" |
||||||
|
"github.com/grafana/grafana/pkg/plugins/pluginrequestmeta" |
||||||
|
) |
||||||
|
|
||||||
|
// NewStatusSourceMiddleware returns a new plugins.ClientMiddleware that sets the status source in the
|
||||||
|
// plugin request meta stored in the context.Context, according to the query data responses returned by QueryError.
|
||||||
|
// If at least one query data response has a "downstream" status source and there isn't one with a "plugin" status source,
|
||||||
|
// the plugin request meta in the context is set to "downstream".
|
||||||
|
func NewStatusSourceMiddleware() plugins.ClientMiddleware { |
||||||
|
return plugins.ClientMiddlewareFunc(func(next plugins.Client) plugins.Client { |
||||||
|
return &StatusSourceMiddleware{ |
||||||
|
next: next, |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
type StatusSourceMiddleware struct { |
||||||
|
next plugins.Client |
||||||
|
} |
||||||
|
|
||||||
|
func (m *StatusSourceMiddleware) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { |
||||||
|
resp, err := m.next.QueryData(ctx, req) |
||||||
|
if resp == nil || len(resp.Responses) == 0 { |
||||||
|
return resp, err |
||||||
|
} |
||||||
|
|
||||||
|
// Set downstream status source in the context if there's at least one response with downstream status source,
|
||||||
|
// and if there's no plugin error
|
||||||
|
var hasPluginError bool |
||||||
|
var hasDownstreamError bool |
||||||
|
for _, r := range resp.Responses { |
||||||
|
if r.Error == nil { |
||||||
|
continue |
||||||
|
} |
||||||
|
if r.ErrorSource == backend.ErrorSourceDownstream { |
||||||
|
hasDownstreamError = true |
||||||
|
} else { |
||||||
|
hasPluginError = true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// A plugin error has higher priority than a downstream error,
|
||||||
|
// so set to downstream only if there's no plugin error
|
||||||
|
if hasDownstreamError && !hasPluginError { |
||||||
|
if err := pluginrequestmeta.WithDownstreamStatusSource(ctx); err != nil { |
||||||
|
return resp, fmt.Errorf("failed to set downstream status source: %w", err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return resp, err |
||||||
|
} |
||||||
|
|
||||||
|
func (m *StatusSourceMiddleware) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error { |
||||||
|
return m.next.CallResource(ctx, req, sender) |
||||||
|
} |
||||||
|
|
||||||
|
func (m *StatusSourceMiddleware) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { |
||||||
|
return m.next.CheckHealth(ctx, req) |
||||||
|
} |
||||||
|
|
||||||
|
func (m *StatusSourceMiddleware) CollectMetrics(ctx context.Context, req *backend.CollectMetricsRequest) (*backend.CollectMetricsResult, error) { |
||||||
|
return m.next.CollectMetrics(ctx, req) |
||||||
|
} |
||||||
|
|
||||||
|
func (m *StatusSourceMiddleware) SubscribeStream(ctx context.Context, req *backend.SubscribeStreamRequest) (*backend.SubscribeStreamResponse, error) { |
||||||
|
return m.next.SubscribeStream(ctx, req) |
||||||
|
} |
||||||
|
|
||||||
|
func (m *StatusSourceMiddleware) PublishStream(ctx context.Context, req *backend.PublishStreamRequest) (*backend.PublishStreamResponse, error) { |
||||||
|
return m.next.PublishStream(ctx, req) |
||||||
|
} |
||||||
|
|
||||||
|
func (m *StatusSourceMiddleware) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error { |
||||||
|
return m.next.RunStream(ctx, req, sender) |
||||||
|
} |
||||||
@ -0,0 +1,88 @@ |
|||||||
|
package clientmiddleware |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"errors" |
||||||
|
"testing" |
||||||
|
|
||||||
|
"github.com/grafana/grafana-plugin-sdk-go/backend" |
||||||
|
"github.com/stretchr/testify/require" |
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/plugins/manager/client/clienttest" |
||||||
|
"github.com/grafana/grafana/pkg/plugins/pluginrequestmeta" |
||||||
|
) |
||||||
|
|
||||||
|
func TestStatusSourceMiddleware(t *testing.T) { |
||||||
|
someErr := errors.New("oops") |
||||||
|
|
||||||
|
for _, tc := range []struct { |
||||||
|
name string |
||||||
|
|
||||||
|
queryDataResponse *backend.QueryDataResponse |
||||||
|
|
||||||
|
expStatusSource pluginrequestmeta.StatusSource |
||||||
|
}{ |
||||||
|
{ |
||||||
|
name: `no error should be "plugin" status source`, |
||||||
|
queryDataResponse: nil, |
||||||
|
expStatusSource: pluginrequestmeta.StatusSourcePlugin, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: `single downstream error should be "downstream" status source`, |
||||||
|
queryDataResponse: &backend.QueryDataResponse{ |
||||||
|
Responses: map[string]backend.DataResponse{ |
||||||
|
"A": {Error: someErr, ErrorSource: backend.ErrorSourceDownstream}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
expStatusSource: pluginrequestmeta.StatusSourceDownstream, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: `single plugin error should be "plugin" status source`, |
||||||
|
queryDataResponse: &backend.QueryDataResponse{ |
||||||
|
Responses: map[string]backend.DataResponse{ |
||||||
|
"A": {Error: someErr, ErrorSource: backend.ErrorSourcePlugin}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
expStatusSource: pluginrequestmeta.StatusSourcePlugin, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: `multiple downstream errors should be "downstream" status source`, |
||||||
|
queryDataResponse: &backend.QueryDataResponse{ |
||||||
|
Responses: map[string]backend.DataResponse{ |
||||||
|
"A": {Error: someErr, ErrorSource: backend.ErrorSourceDownstream}, |
||||||
|
"B": {Error: someErr, ErrorSource: backend.ErrorSourceDownstream}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
expStatusSource: pluginrequestmeta.StatusSourceDownstream, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: `single plugin error mixed with downstream errors should be "plugin" status source`, |
||||||
|
queryDataResponse: &backend.QueryDataResponse{ |
||||||
|
Responses: map[string]backend.DataResponse{ |
||||||
|
"A": {Error: someErr, ErrorSource: backend.ErrorSourceDownstream}, |
||||||
|
"B": {Error: someErr, ErrorSource: backend.ErrorSourcePlugin}, |
||||||
|
"C": {Error: someErr, ErrorSource: backend.ErrorSourceDownstream}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
expStatusSource: pluginrequestmeta.StatusSourcePlugin, |
||||||
|
}, |
||||||
|
} { |
||||||
|
t.Run(tc.name, func(t *testing.T) { |
||||||
|
cdt := clienttest.NewClientDecoratorTest(t, |
||||||
|
clienttest.WithMiddlewares( |
||||||
|
NewPluginRequestMetaMiddleware(), |
||||||
|
NewStatusSourceMiddleware(), |
||||||
|
), |
||||||
|
) |
||||||
|
cdt.TestClient.QueryDataFunc = func(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { |
||||||
|
cdt.QueryDataCtx = ctx |
||||||
|
return tc.queryDataResponse, nil |
||||||
|
} |
||||||
|
|
||||||
|
_, _ = cdt.Decorator.QueryData(context.Background(), &backend.QueryDataRequest{}) |
||||||
|
|
||||||
|
ss := pluginrequestmeta.StatusSourceFromContext(cdt.QueryDataCtx) |
||||||
|
require.Equal(t, tc.expStatusSource, ss) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue