package pluginsintegration import ( "context" "encoding/json" "path/filepath" "testing" "time" "github.com/grafana/grafana-azure-sdk-go/azsettings" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" "github.com/stretchr/testify/require" "gopkg.in/ini.v1" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin" "github.com/grafana/grafana/pkg/plugins/manager/registry" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" "github.com/grafana/grafana/pkg/services/searchV2" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tsdb/azuremonitor" cloudmonitoring "github.com/grafana/grafana/pkg/tsdb/cloud-monitoring" "github.com/grafana/grafana/pkg/tsdb/cloudwatch" "github.com/grafana/grafana/pkg/tsdb/elasticsearch" postgres "github.com/grafana/grafana/pkg/tsdb/grafana-postgresql-datasource" pyroscope "github.com/grafana/grafana/pkg/tsdb/grafana-pyroscope-datasource" testdatasource "github.com/grafana/grafana/pkg/tsdb/grafana-testdata-datasource" "github.com/grafana/grafana/pkg/tsdb/grafanads" "github.com/grafana/grafana/pkg/tsdb/graphite" "github.com/grafana/grafana/pkg/tsdb/influxdb" "github.com/grafana/grafana/pkg/tsdb/loki" "github.com/grafana/grafana/pkg/tsdb/mssql" "github.com/grafana/grafana/pkg/tsdb/mysql" "github.com/grafana/grafana/pkg/tsdb/opentsdb" "github.com/grafana/grafana/pkg/tsdb/parca" "github.com/grafana/grafana/pkg/tsdb/prometheus" "github.com/grafana/grafana/pkg/tsdb/tempo" ) func TestIntegrationPluginManager(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } staticRootPath, err := filepath.Abs("../../../public/") require.NoError(t, err) bundledPluginsPath, err := filepath.Abs("../../../plugins-bundled/internal") require.NoError(t, err) // We use the raw config here as it forms the basis for the setting.Provider implementation // The plugin manager also relies directly on the setting.Cfg struct to provide Grafana specific // properties such as the loading paths raw, err := ini.Load([]byte(` app_mode = production [plugin.test-app] path=../../plugins/manager/testdata/test-app [plugin.test-panel] not=included `), ) require.NoError(t, err) features := featuremgmt.WithFeatures() cfg := &setting.Cfg{ Raw: raw, StaticRootPath: staticRootPath, BundledPluginsPath: bundledPluginsPath, Azure: &azsettings.AzureSettings{}, // nolint:staticcheck IsFeatureToggleEnabled: features.IsEnabledGlobally, } tracer := tracing.InitializeTracerForTest() hcp := httpclient.NewProvider() am := azuremonitor.ProvideService(hcp) cw := cloudwatch.ProvideService(cfg, hcp, features) cm := cloudmonitoring.ProvideService(hcp, tracer) es := elasticsearch.ProvideService(hcp, tracer) grap := graphite.ProvideService(hcp, tracer) idb := influxdb.ProvideService(hcp, features) lk := loki.ProvideService(hcp, features, tracer) otsdb := opentsdb.ProvideService(hcp) pr := prometheus.ProvideService(hcp, cfg, features) tmpo := tempo.ProvideService(hcp) td := testdatasource.ProvideService() pg := postgres.ProvideService(cfg) my := mysql.ProvideService(cfg, hcp) ms := mssql.ProvideService(cfg) sv2 := searchV2.ProvideService(cfg, db.InitTestDB(t), nil, nil, tracer, features, nil, nil, nil) graf := grafanads.ProvideService(sv2, nil) pyroscope := pyroscope.ProvideService(hcp) parca := parca.ProvideService(hcp) coreRegistry := coreplugin.ProvideCoreRegistry(tracing.InitializeTracerForTest(), am, cw, cm, es, grap, idb, lk, otsdb, pr, tmpo, td, pg, my, ms, graf, pyroscope, parca) testCtx := CreateIntegrationTestCtx(t, cfg, coreRegistry) ctx := context.Background() verifyCorePluginCatalogue(t, ctx, testCtx.PluginStore) verifyBundledPlugins(t, ctx, testCtx.PluginStore) verifyPluginStaticRoutes(t, ctx, testCtx.PluginStore, testCtx.PluginRegistry) verifyBackendProcesses(t, testCtx.PluginRegistry.Plugins(ctx)) verifyPluginQuery(t, ctx, testCtx.PluginClient) } func verifyPluginQuery(t *testing.T, ctx context.Context, c plugins.Client) { now := time.Unix(1661420870, 0) req := &backend.QueryDataRequest{ PluginContext: backend.PluginContext{ PluginID: "grafana-testdata-datasource", }, Queries: []backend.DataQuery{ { RefID: "A", TimeRange: backend.TimeRange{ From: now.Add(-5 * time.Minute), To: now, }, JSON: json.RawMessage(`{"scenarioId":"csv_metric_values","stringInput":"1,20,90,30,5,0"}`), }, }, } resp, err := c.QueryData(ctx, req) require.NoError(t, err) payload, err := resp.MarshalJSON() require.NoError(t, err) require.JSONEq(t, `{"results":{"A":{"frames":[{"schema":{"refId":"A","fields":[{"name":"time","type":"time","typeInfo":{"frame":"time.Time"}},{"name":"A-series","type":"number","typeInfo":{"frame":"int64","nullable":true}}]},"data":{"values":[[1661420570000,1661420630000,1661420690000,1661420750000,1661420810000,1661420870000],[1,20,90,30,5,0]]}}],"status":200}}}`, string(payload)) } func verifyCorePluginCatalogue(t *testing.T, ctx context.Context, ps *pluginstore.Service) { t.Helper() expPanels := map[string]struct{}{ "alertGroups": {}, "alertlist": {}, "annolist": {}, "barchart": {}, "bargauge": {}, "canvas": {}, "dashlist": {}, "debug": {}, "gauge": {}, "geomap": {}, "gettingstarted": {}, "graph": {}, "heatmap": {}, "histogram": {}, "live": {}, "logs": {}, "candlestick": {}, "news": {}, "nodeGraph": {}, "flamegraph": {}, "traces": {}, "piechart": {}, "stat": {}, "state-timeline": {}, "status-history": {}, "table": {}, "table-old": {}, "text": {}, "timeseries": {}, "trend": {}, "welcome": {}, "xychart": {}, "datagrid": {}, } expDataSources := map[string]struct{}{ "cloudwatch": {}, "stackdriver": {}, "grafana-azure-monitor-datasource": {}, "elasticsearch": {}, "graphite": {}, "influxdb": {}, "loki": {}, "opentsdb": {}, "prometheus": {}, "tempo": {}, "grafana-testdata-datasource": {}, "grafana-postgresql-datasource": {}, "mysql": {}, "mssql": {}, "grafana": {}, "alertmanager": {}, "dashboard": {}, "input": {}, "jaeger": {}, "mixed": {}, "zipkin": {}, "grafana-pyroscope-datasource": {}, "parca": {}, } expApps := map[string]struct{}{ "test-app": {}, } panels := ps.Plugins(ctx, plugins.TypePanel) require.Equal(t, len(expPanels), len(panels)) for _, p := range panels { p, exists := ps.Plugin(ctx, p.ID) require.NotEqual(t, pluginstore.Plugin{}, p) require.True(t, exists) require.Contains(t, expPanels, p.ID) } dataSources := ps.Plugins(ctx, plugins.TypeDataSource) require.Equal(t, len(expDataSources), len(dataSources)) for _, ds := range dataSources { p, exists := ps.Plugin(ctx, ds.ID) require.NotEqual(t, pluginstore.Plugin{}, p) require.True(t, exists) require.Contains(t, expDataSources, ds.ID) } apps := ps.Plugins(ctx, plugins.TypeApp) require.Equal(t, len(expApps), len(apps)) for _, app := range apps { p, exists := ps.Plugin(ctx, app.ID) require.True(t, exists) require.NotNil(t, p) require.Contains(t, expApps, app.ID) } require.Equal(t, len(expPanels)+len(expDataSources)+len(expApps), len(ps.Plugins(ctx))) } func verifyBundledPlugins(t *testing.T, ctx context.Context, ps *pluginstore.Service) { t.Helper() dsPlugins := make(map[string]struct{}) for _, p := range ps.Plugins(ctx, plugins.TypeDataSource) { dsPlugins[p.ID] = struct{}{} } inputPlugin, exists := ps.Plugin(ctx, "input") require.True(t, exists) require.NotEqual(t, pluginstore.Plugin{}, inputPlugin) require.NotNil(t, dsPlugins["input"]) pluginRoutes := make(map[string]*plugins.StaticRoute) for _, r := range ps.Routes(ctx) { pluginRoutes[r.PluginID] = r } for _, pluginID := range []string{"input"} { require.Contains(t, pluginRoutes, pluginID) require.Equal(t, pluginRoutes[pluginID].Directory, inputPlugin.Base()) } } func verifyPluginStaticRoutes(t *testing.T, ctx context.Context, rr plugins.StaticRouteResolver, reg registry.Service) { routes := make(map[string]*plugins.StaticRoute) for _, route := range rr.Routes(ctx) { routes[route.PluginID] = route } require.Len(t, routes, 2) inputPlugin, _ := reg.Plugin(ctx, "input") require.NotNil(t, routes["input"]) require.Equal(t, routes["input"].Directory, inputPlugin.FS.Base()) testAppPlugin, _ := reg.Plugin(ctx, "test-app") require.Contains(t, routes, "test-app") require.Equal(t, routes["test-app"].Directory, testAppPlugin.FS.Base()) } func verifyBackendProcesses(t *testing.T, ps []*plugins.Plugin) { for _, p := range ps { if p.Backend { pc, exists := p.Client() require.True(t, exists) require.NotNil(t, pc) } } }