diff --git a/pkg/plugins/backendplugin/backendplugin.go b/pkg/plugins/backendplugin/backendplugin.go index 2d8f6de2c27..baf755f88fa 100644 --- a/pkg/plugins/backendplugin/backendplugin.go +++ b/pkg/plugins/backendplugin/backendplugin.go @@ -3,7 +3,8 @@ package backendplugin import ( "github.com/grafana/grafana/pkg/plugins/log" + "go.opentelemetry.io/otel/trace" ) // PluginFactoryFunc is a function type for creating a Plugin. -type PluginFactoryFunc func(pluginID string, logger log.Logger, env func() []string) (Plugin, error) +type PluginFactoryFunc func(pluginID string, logger log.Logger, tracer trace.Tracer, env func() []string) (Plugin, error) diff --git a/pkg/plugins/backendplugin/coreplugin/core_plugin.go b/pkg/plugins/backendplugin/coreplugin/core_plugin.go index 6010b2650c5..0cb85b38c89 100644 --- a/pkg/plugins/backendplugin/coreplugin/core_plugin.go +++ b/pkg/plugins/backendplugin/coreplugin/core_plugin.go @@ -4,6 +4,7 @@ import ( "context" "github.com/grafana/grafana-plugin-sdk-go/backend" + "go.opentelemetry.io/otel/trace" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin" @@ -24,7 +25,7 @@ type corePlugin struct { // New returns a new backendplugin.PluginFactoryFunc for creating a core (built-in) backendplugin.Plugin. func New(opts backend.ServeOpts) backendplugin.PluginFactoryFunc { - return func(pluginID string, logger log.Logger, _ func() []string) (backendplugin.Plugin, error) { + return func(pluginID string, logger log.Logger, _ trace.Tracer, _ func() []string) (backendplugin.Plugin, error) { return &corePlugin{ pluginID: pluginID, logger: logger, diff --git a/pkg/plugins/backendplugin/coreplugin/core_plugin_test.go b/pkg/plugins/backendplugin/coreplugin/core_plugin_test.go index c3243c52ae9..6b4cc5a385a 100644 --- a/pkg/plugins/backendplugin/coreplugin/core_plugin_test.go +++ b/pkg/plugins/backendplugin/coreplugin/core_plugin_test.go @@ -8,13 +8,14 @@ import ( "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin" "github.com/grafana/grafana/pkg/plugins/log" + "github.com/grafana/grafana/pkg/plugins/manager/fakes" "github.com/stretchr/testify/require" ) func TestCorePlugin(t *testing.T) { t.Run("New core plugin with empty opts should return expected values", func(t *testing.T) { factory := coreplugin.New(backend.ServeOpts{}) - p, err := factory("plugin", log.New("test"), nil) + p, err := factory("plugin", log.New("test"), fakes.InitializeNoopTracerForTest(), nil) require.NoError(t, err) require.NotNil(t, p) require.NoError(t, p.Start(context.Background())) @@ -47,7 +48,7 @@ func TestCorePlugin(t *testing.T) { return nil }), }) - p, err := factory("plugin", log.New("test"), nil) + p, err := factory("plugin", log.New("test"), fakes.InitializeNoopTracerForTest(), nil) require.NoError(t, err) require.NotNil(t, p) require.NoError(t, p.Start(context.Background())) diff --git a/pkg/plugins/backendplugin/coreplugin/registry.go b/pkg/plugins/backendplugin/coreplugin/registry.go index 23dc4524f75..423d74ac29d 100644 --- a/pkg/plugins/backendplugin/coreplugin/registry.go +++ b/pkg/plugins/backendplugin/coreplugin/registry.go @@ -254,7 +254,7 @@ func NewPlugin(pluginID string, cfg *setting.Cfg, httpClientProvider *httpclient if backendFactory == nil { return nil, ErrCorePluginNotFound } - bp, err := backendFactory(p.ID, p.Logger(), nil) + bp, err := backendFactory(p.ID, p.Logger(), tracer, nil) if err != nil { return nil, err } diff --git a/pkg/plugins/backendplugin/grpcplugin/client.go b/pkg/plugins/backendplugin/grpcplugin/client.go index 5ce776ec757..35920fef2cf 100644 --- a/pkg/plugins/backendplugin/grpcplugin/client.go +++ b/pkg/plugins/backendplugin/grpcplugin/client.go @@ -6,6 +6,8 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin" goplugin "github.com/hashicorp/go-plugin" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + trace "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/embedded" "google.golang.org/grpc" "github.com/grafana/grafana/pkg/plugins/backendplugin" @@ -40,7 +42,20 @@ var pluginSet = map[int]goplugin.PluginSet{ }, } -func newClientConfig(executablePath string, args []string, env []string, skipHostEnvVars bool, logger log.Logger, +type clientTracerProvider struct { + tracer trace.Tracer + embedded.TracerProvider +} + +func (ctp *clientTracerProvider) Tracer(instrumentationName string, opts ...trace.TracerOption) trace.Tracer { + return ctp.tracer +} + +func newClientTracerProvider(tracer trace.Tracer) trace.TracerProvider { + return &clientTracerProvider{tracer: tracer} +} + +func newClientConfig(executablePath string, args []string, env []string, skipHostEnvVars bool, logger log.Logger, tracer trace.Tracer, versionedPlugins map[int]goplugin.PluginSet) *goplugin.ClientConfig { // We can ignore gosec G201 here, since the dynamic part of executablePath comes from the plugin definition // nolint:gosec @@ -55,7 +70,13 @@ func newClientConfig(executablePath string, args []string, env []string, skipHos Logger: logWrapper{Logger: logger}, AllowedProtocols: []goplugin.Protocol{goplugin.ProtocolGRPC}, GRPCDialOptions: []grpc.DialOption{ - grpc.WithStatsHandler(otelgrpc.NewClientHandler()), + // https://github.com/grafana/app-platform-wg/issues/140 + // external plugins are loaded before k8s API server + // configures the tracing service thus failing to + // record trace span in the middleware. + // With code below we are passing the same tracer that k8s API server + // uses so that middleware is configured with tracer. + grpc.WithStatsHandler(otelgrpc.NewClientHandler(otelgrpc.WithTracerProvider(newClientTracerProvider(tracer)))), }, } } diff --git a/pkg/plugins/backendplugin/grpcplugin/client_proto.go b/pkg/plugins/backendplugin/grpcplugin/client_proto.go index 19c83d0c7dd..9922258f9e2 100644 --- a/pkg/plugins/backendplugin/grpcplugin/client_proto.go +++ b/pkg/plugins/backendplugin/grpcplugin/client_proto.go @@ -4,6 +4,7 @@ import ( "context" "errors" + trace "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" "github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2" @@ -44,6 +45,7 @@ type ProtoClientOpts struct { ExecutableArgs []string Env []string Logger log.Logger + Tracer trace.Tracer } func NewProtoClient(opts ProtoClientOpts) (ProtoClient, error) { @@ -56,6 +58,7 @@ func NewProtoClient(opts ProtoClientOpts) (ProtoClient, error) { versionedPlugins: pluginSet, }, opts.Logger, + opts.Tracer, func() []string { return opts.Env }, ) diff --git a/pkg/plugins/backendplugin/grpcplugin/grpc_plugin.go b/pkg/plugins/backendplugin/grpcplugin/grpc_plugin.go index bce188928aa..d1c6152a0ef 100644 --- a/pkg/plugins/backendplugin/grpcplugin/grpc_plugin.go +++ b/pkg/plugins/backendplugin/grpcplugin/grpc_plugin.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/hashicorp/go-plugin" + trace "go.opentelemetry.io/otel/trace" "github.com/grafana/grafana/pkg/infra/process" "github.com/grafana/grafana/pkg/plugins" @@ -37,17 +38,17 @@ const ( // newPlugin allocates and returns a new gRPC (external) backendplugin.Plugin. func newPlugin(descriptor PluginDescriptor) backendplugin.PluginFactoryFunc { - return func(pluginID string, logger log.Logger, env func() []string) (backendplugin.Plugin, error) { - return newGrpcPlugin(descriptor, logger, env), nil + return func(pluginID string, logger log.Logger, tracer trace.Tracer, env func() []string) (backendplugin.Plugin, error) { + return newGrpcPlugin(descriptor, logger, tracer, env), nil } } -func newGrpcPlugin(descriptor PluginDescriptor, logger log.Logger, env func() []string) *grpcPlugin { +func newGrpcPlugin(descriptor PluginDescriptor, logger log.Logger, tracer trace.Tracer, env func() []string) *grpcPlugin { return &grpcPlugin{ descriptor: descriptor, logger: logger, clientFactory: func() *plugin.Client { - return plugin.NewClient(newClientConfig(descriptor.executablePath, descriptor.executableArgs, env(), descriptor.skipHostEnvVars, logger, descriptor.versionedPlugins)) + return plugin.NewClient(newClientConfig(descriptor.executablePath, descriptor.executableArgs, env(), descriptor.skipHostEnvVars, logger, tracer, descriptor.versionedPlugins)) }, state: pluginStateNotStarted, } diff --git a/pkg/plugins/manager/fakes/fakes.go b/pkg/plugins/manager/fakes/fakes.go index 2ba324f311d..f25daddc504 100644 --- a/pkg/plugins/manager/fakes/fakes.go +++ b/pkg/plugins/manager/fakes/fakes.go @@ -8,6 +8,8 @@ import ( "sync" "github.com/grafana/grafana-plugin-sdk-go/backend" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/auth" @@ -267,6 +269,14 @@ func (r *FakePluginRepo) PluginVersion(ctx context.Context, pluginID, version st return repo.VersionData{}, nil } +type fakeTracerProvider struct { + noop.TracerProvider +} + +func InitializeNoopTracerForTest() trace.Tracer { + return fakeTracerProvider{}.Tracer("test") +} + type FakePluginStorage struct { ExtractFunc func(_ context.Context, pluginID string, dirNameFunc storage.DirNameGeneratorFunc, z *zip.ReadCloser) (*storage.ExtractedPluginArchive, error) } @@ -340,7 +350,7 @@ func NewFakeBackendProcessProvider() *FakeBackendProcessProvider { } f.BackendFactoryFunc = func(ctx context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc { f.Requested[p.ID]++ - return func(pluginID string, _ log.Logger, _ func() []string) (backendplugin.Plugin, error) { + return func(pluginID string, _ log.Logger, _ trace.Tracer, _ func() []string) (backendplugin.Plugin, error) { f.Invoked[pluginID]++ return &FakePluginClient{}, nil } diff --git a/pkg/plugins/manager/pipeline/initialization/steps.go b/pkg/plugins/manager/pipeline/initialization/steps.go index 2df09d1144a..02573f6ba20 100644 --- a/pkg/plugins/manager/pipeline/initialization/steps.go +++ b/pkg/plugins/manager/pipeline/initialization/steps.go @@ -9,6 +9,7 @@ import ( "github.com/grafana/grafana/pkg/plugins/log" "github.com/grafana/grafana/pkg/plugins/manager/process" "github.com/grafana/grafana/pkg/plugins/manager/registry" + "go.opentelemetry.io/otel/trace" ) // BackendClientInit implements an InitializeFunc for initializing a backend plugin process. @@ -21,20 +22,22 @@ type BackendClientInit struct { envVarProvider envvars.Provider backendProvider plugins.BackendFactoryProvider log log.Logger + tracer trace.Tracer } // BackendClientInitStep returns a new InitializeFunc for registering a backend plugin process. func BackendClientInitStep(envVarProvider envvars.Provider, - backendProvider plugins.BackendFactoryProvider) InitializeFunc { - return newBackendProcessRegistration(envVarProvider, backendProvider).Initialize + backendProvider plugins.BackendFactoryProvider, tracer trace.Tracer) InitializeFunc { + return newBackendProcessRegistration(envVarProvider, backendProvider, tracer).Initialize } func newBackendProcessRegistration(envVarProvider envvars.Provider, - backendProvider plugins.BackendFactoryProvider) *BackendClientInit { + backendProvider plugins.BackendFactoryProvider, tracer trace.Tracer) *BackendClientInit { return &BackendClientInit{ backendProvider: backendProvider, envVarProvider: envVarProvider, log: log.New("plugins.backend.registration"), + tracer: tracer, } } @@ -49,7 +52,7 @@ func (b *BackendClientInit) Initialize(ctx context.Context, p *plugins.Plugin) ( // this will ensure that the env variables are calculated every time a plugin is started envFunc := func() []string { return b.envVarProvider.PluginEnvVars(ctx, p) } - if backendClient, err := backendFactory(p.ID, p.Logger(), envFunc); err != nil { + if backendClient, err := backendFactory(p.ID, p.Logger(), b.tracer, envFunc); err != nil { return nil, err } else { p.RegisterClient(backendClient) diff --git a/pkg/plugins/manager/pipeline/initialization/steps_test.go b/pkg/plugins/manager/pipeline/initialization/steps_test.go index b5e43195e71..da00444e93e 100644 --- a/pkg/plugins/manager/pipeline/initialization/steps_test.go +++ b/pkg/plugins/manager/pipeline/initialization/steps_test.go @@ -4,11 +4,12 @@ import ( "context" "testing" - "github.com/stretchr/testify/require" - "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/log" + "github.com/grafana/grafana/pkg/plugins/manager/fakes" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace" ) func TestInitializer_Initialize(t *testing.T) { @@ -28,7 +29,7 @@ func TestInitializer_Initialize(t *testing.T) { Class: plugins.ClassCore, } - stepFunc := BackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{plugin: p}) + stepFunc := BackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{plugin: p}, fakes.InitializeNoopTracerForTest()) var err error p, err = stepFunc(context.Background(), p) @@ -52,7 +53,7 @@ func TestInitializer_Initialize(t *testing.T) { Class: plugins.ClassExternal, } - stepFunc := BackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{plugin: p}) + stepFunc := BackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{plugin: p}, fakes.InitializeNoopTracerForTest()) var err error p, err = stepFunc(context.Background(), p) @@ -76,7 +77,7 @@ func TestInitializer_Initialize(t *testing.T) { Class: plugins.ClassExternal, } - stepFunc := BackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{plugin: p}) + stepFunc := BackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{plugin: p}, fakes.InitializeNoopTracerForTest()) var err error p, err = stepFunc(context.Background(), p) @@ -96,7 +97,7 @@ func TestInitializer_Initialize(t *testing.T) { i := BackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{ plugin: p, - }) + }, fakes.InitializeNoopTracerForTest()) var err error p, err = i(context.Background(), p) @@ -115,7 +116,7 @@ type fakeBackendProvider struct { } func (f *fakeBackendProvider) BackendFactory(_ context.Context, _ *plugins.Plugin) backendplugin.PluginFactoryFunc { - return func(_ string, _ log.Logger, _ func() []string) (backendplugin.Plugin, error) { + return func(_ string, _ log.Logger, _ trace.Tracer, _ func() []string) (backendplugin.Plugin, error) { return f.plugin, nil } } diff --git a/pkg/services/pluginsintegration/loader/loader_test.go b/pkg/services/pluginsintegration/loader/loader_test.go index 4eafd7db235..677b07bcb27 100644 --- a/pkg/services/pluginsintegration/loader/loader_test.go +++ b/pkg/services/pluginsintegration/loader/loader_test.go @@ -10,6 +10,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/plugins" @@ -584,7 +585,7 @@ func TestLoader_Load_ExternalRegistration(t *testing.T) { backendFactoryProvider := fakes.NewFakeBackendProcessProvider() backendFactoryProvider.BackendFactoryFunc = func(ctx context.Context, plugin *plugins.Plugin) backendplugin.PluginFactoryFunc { - return func(pluginID string, logger log.Logger, env func() []string) (backendplugin.Plugin, error) { + return func(pluginID string, logger log.Logger, tracer trace.Tracer, env func() []string) (backendplugin.Plugin, error) { require.Equal(t, "grafana-test-datasource", pluginID) return &fakes.FakeBackendPlugin{}, nil } @@ -1133,7 +1134,7 @@ func TestLoader_Load_SkipUninitializedPlugins(t *testing.T) { procPrvdr := fakes.NewFakeBackendProcessProvider() // Cause an initialization error procPrvdr.BackendFactoryFunc = func(ctx context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc { - return func(pluginID string, _ log.Logger, _ func() []string) (backendplugin.Plugin, error) { + return func(pluginID string, _ log.Logger, _ trace.Tracer, _ func() []string) (backendplugin.Plugin, error) { if pluginID == "test-datasource" { return nil, errors.New("failed to initialize") } diff --git a/pkg/services/pluginsintegration/pipeline/pipeline.go b/pkg/services/pluginsintegration/pipeline/pipeline.go index 6422c7ddd61..a590d8c810a 100644 --- a/pkg/services/pluginsintegration/pipeline/pipeline.go +++ b/pkg/services/pluginsintegration/pipeline/pipeline.go @@ -68,7 +68,7 @@ func ProvideInitializationStage(cfg *config.PluginManagementCfg, pr registry.Ser return initialization.New(cfg, initialization.Opts{ InitializeFuncs: []initialization.InitializeFunc{ ExternalServiceRegistrationStep(cfg, externalServiceRegistry, tracer), - initialization.BackendClientInitStep(pluginEnvProvider, bp), + initialization.BackendClientInitStep(pluginEnvProvider, bp, tracer), initialization.BackendProcessStartStep(pm), RegisterPluginRolesStep(roleRegistry), RegisterActionSetsStep(actionSetRegistry), diff --git a/pkg/services/pluginsintegration/renderer/renderer.go b/pkg/services/pluginsintegration/renderer/renderer.go index 9eb71fc7d7d..ff8003b957c 100644 --- a/pkg/services/pluginsintegration/renderer/renderer.go +++ b/pkg/services/pluginsintegration/renderer/renderer.go @@ -5,6 +5,7 @@ import ( "errors" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2" "github.com/grafana/grafana/pkg/plugins/backendplugin/provider" @@ -22,11 +23,12 @@ import ( "github.com/grafana/grafana/pkg/services/pluginsintegration/pipeline" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginerrs" "github.com/grafana/grafana/pkg/services/rendering" + "go.opentelemetry.io/otel/trace" ) func ProvideService(cfg *config.PluginManagementCfg, pluginEnvProvider envvars.Provider, - registry registry.Service) (*Manager, error) { - l, err := createLoader(cfg, pluginEnvProvider, registry) + registry registry.Service, tracer tracing.Tracer) (*Manager, error) { + l, err := createLoader(cfg, pluginEnvProvider, registry, tracer) if err != nil { return nil, err } @@ -105,7 +107,7 @@ func (m *Manager) Renderer(ctx context.Context) (rendering.Plugin, bool) { } func createLoader(cfg *config.PluginManagementCfg, pluginEnvProvider envvars.Provider, - pr registry.Service) (loader.Service, error) { + pr registry.Service, tracer trace.Tracer) (loader.Service, error) { d := discovery.New(cfg, discovery.Opts{ FindFilterFuncs: []discovery.FindFilterFunc{ discovery.NewPermittedPluginTypesFilterStep([]plugins.Type{plugins.TypeRenderer}), @@ -124,7 +126,7 @@ func createLoader(cfg *config.PluginManagementCfg, pluginEnvProvider envvars.Pro }) i := initialization.New(cfg, initialization.Opts{ InitializeFuncs: []initialization.InitializeFunc{ - initialization.BackendClientInitStep(pluginEnvProvider, provider.New(provider.RendererProvider)), + initialization.BackendClientInitStep(pluginEnvProvider, provider.New(provider.RendererProvider), tracer), initialization.PluginRegistrationStep(pr), }, })