diff --git a/pkg/expr/nodes.go b/pkg/expr/nodes.go index 44455f0bb9f..83212715625 100644 --- a/pkg/expr/nodes.go +++ b/pkg/expr/nodes.go @@ -408,7 +408,12 @@ func (dn *DSNode) Execute(ctx context.Context, now time.Time, _ mathexp.Vars, s } } else { // transform request from backend.QueryDataRequest to k8s request - k8sReq := &data.QueryDataRequest{} + k8sReq := &data.QueryDataRequest{ + TimeRange: data.TimeRange{ + From: req.Queries[0].TimeRange.From.Format(time.RFC3339), + To: req.Queries[0].TimeRange.To.Format(time.RFC3339), + }, + } for _, q := range req.Queries { var dataQuery data.DataQuery err := json.Unmarshal(q.JSON, &dataQuery) diff --git a/pkg/services/mtdsclient/mt_datasource_client_builder.go b/pkg/services/mtdsclient/mt_datasource_client_builder.go index 2499812b9d3..d5108aef4e2 100644 --- a/pkg/services/mtdsclient/mt_datasource_client_builder.go +++ b/pkg/services/mtdsclient/mt_datasource_client_builder.go @@ -64,3 +64,23 @@ func NewMtDatasourceClientBuilderWithClientSupplier( logger: logger, } } + +func NewTestMTDSClientBuilder(isMultiTenant bool, mockClient clientapi.QueryDataClient) MTDatasourceClientBuilder { + return &testBuilder{ + mockClient: mockClient, + isMultitenant: isMultiTenant, + } +} + +type testBuilder struct { + mockClient clientapi.QueryDataClient + isMultitenant bool +} + +func (b *testBuilder) BuildClient(pluginId string, uid string) (clientapi.QueryDataClient, bool) { + if !b.isMultitenant { + return nil, false + } + + return b.mockClient, true +} diff --git a/pkg/services/query/query.go b/pkg/services/query/query.go index 4cc718e2cf6..d3f05438e45 100644 --- a/pkg/services/query/query.go +++ b/pkg/services/query/query.go @@ -295,7 +295,12 @@ func (s *ServiceImpl) handleQuerySingleDatasource(ctx context.Context, user iden return s.pluginClient.QueryData(ctx, req) } else { // multi tenant flow // transform request from backend.QueryDataRequest to k8s request - k8sReq := &data.QueryDataRequest{} + k8sReq := &data.QueryDataRequest{ + TimeRange: data.TimeRange{ + From: req.Queries[0].TimeRange.From.Format(time.RFC3339), + To: req.Queries[0].TimeRange.To.Format(time.RFC3339), + }, + } for _, q := range req.Queries { var dataQuery data.DataQuery err := json.Unmarshal(q.JSON, &dataQuery) diff --git a/pkg/services/query/query_test.go b/pkg/services/query/query_test.go index 542e85e8a0b..e4d430aa5b3 100644 --- a/pkg/services/query/query_test.go +++ b/pkg/services/query/query_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/grafana/grafana-plugin-sdk-go/backend" + data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -24,6 +25,7 @@ import ( "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/registry/apis/query/clientapi" "github.com/grafana/grafana/pkg/services/contexthandler" "github.com/grafana/grafana/pkg/services/contexthandler/ctxkey" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" @@ -50,7 +52,7 @@ func TestMain(m *testing.M) { func TestIntegrationParseMetricRequest(t *testing.T) { t.Run("Test a simple single datasource query", func(t *testing.T) { - tc := setup(t) + tc := setup(t, false, nil) mr := metricRequestWithQueries(t, `{ "refId": "A", "datasource": { @@ -74,7 +76,7 @@ func TestIntegrationParseMetricRequest(t *testing.T) { }) t.Run("Test a single datasource query with expressions", func(t *testing.T) { - tc := setup(t) + tc := setup(t, false, nil) mr := metricRequestWithQueries(t, `{ "refId": "A", "datasource": { @@ -114,7 +116,7 @@ func TestIntegrationParseMetricRequest(t *testing.T) { }) t.Run("Test a simple mixed datasource query", func(t *testing.T) { - tc := setup(t) + tc := setup(t, false, nil) mr := metricRequestWithQueries(t, `{ "refId": "A", "datasource": { @@ -147,7 +149,7 @@ func TestIntegrationParseMetricRequest(t *testing.T) { }) t.Run("Test a mixed datasource query with expressions", func(t *testing.T) { - tc := setup(t) + tc := setup(t, false, nil) mr := metricRequestWithQueries(t, `{ "refId": "A", "datasource": { @@ -208,7 +210,7 @@ func TestIntegrationParseMetricRequest(t *testing.T) { }) t.Run("Header validation", func(t *testing.T) { - tc := setup(t) + tc := setup(t, false, nil) mr := metricRequestWithQueries(t, `{ "refId": "A", "datasource": { @@ -250,7 +252,7 @@ func TestIntegrationParseMetricRequest(t *testing.T) { }) t.Run("Test a duplicated refId", func(t *testing.T) { - tc := setup(t) + tc := setup(t, false, nil) mr := metricRequestWithQueries(t, `{ "refId": "A", "datasource": { @@ -271,7 +273,7 @@ func TestIntegrationParseMetricRequest(t *testing.T) { func TestIntegrationQueryDataMultipleSources(t *testing.T) { t.Run("can query multiple datasources", func(t *testing.T) { - tc := setup(t) + tc := setup(t, false, nil) query1, err := simplejson.NewJson([]byte(` { "datasource": { @@ -320,7 +322,7 @@ func TestIntegrationQueryDataMultipleSources(t *testing.T) { }) t.Run("can query multiple datasources with an expression present", func(t *testing.T) { - tc := setup(t) + tc := setup(t, false, nil) query1, err := simplejson.NewJson([]byte(` { "datasource": { @@ -386,7 +388,7 @@ func TestIntegrationQueryDataMultipleSources(t *testing.T) { }) t.Run("error is returned in query when one of the queries fails", func(t *testing.T) { - tc := setup(t) + tc := setup(t, false, nil) query1, _ := simplejson.NewJson([]byte(` { @@ -426,7 +428,7 @@ func TestIntegrationQueryDataMultipleSources(t *testing.T) { }) t.Run("ignores a deprecated datasourceID", func(t *testing.T) { - tc := setup(t) + tc := setup(t, false, nil) query1, err := simplejson.NewJson([]byte(` { "datasource": { @@ -452,7 +454,63 @@ func TestIntegrationQueryDataMultipleSources(t *testing.T) { }) } -func setup(t *testing.T) *testContext { +func TestIntegrationQueryDataWithMTDSClient(t *testing.T) { + t.Run("can run a simple datasource query with a mt ds client", func(t *testing.T) { + stubbedResponse := &backend.QueryDataResponse{Responses: make(backend.Responses)} + testClient := &testClient{ + queryDataStubbedResponse: stubbedResponse, + } + tc := setup(t, true, testClient) + mr := metricRequestWithQueries(t, `{ + "refId": "A", + "datasource": { + "uid": "gIEkMvIVz", + "type": "postgres" + } + }`, `{ + "refId": "B", + "datasource": { + "uid": "gIEkMvIVz", + "type": "postgres" + } + }`) + mr.From = "2022-01-01" + mr.To = "2022-01-02" + ctx := context.Background() + _, err := tc.queryService.QueryData(ctx, tc.signedInUser, true, mr) + require.NoError(t, err) + + assert.Equal(t, data.QueryDataRequest{ + TimeRange: data.TimeRange{ + From: "2022-01-01T00:00:00Z", + To: "2022-01-02T00:00:00Z", + }, + Queries: []data.DataQuery{ + { + CommonQueryProperties: data.CommonQueryProperties{ + RefID: "A", + Datasource: &data.DataSourceRef{ + Type: "postgres", + UID: "gIEkMvIVz", + }, + }, + }, + { + CommonQueryProperties: data.CommonQueryProperties{ + RefID: "B", + Datasource: &data.DataSourceRef{ + Type: "postgres", + UID: "gIEkMvIVz", + }, + }, + }, + }, + Debug: false, + }, testClient.queryDataLastCalledWith) + }) +} + +func setup(t *testing.T, isMultiTenant bool, mockClient clientapi.QueryDataClient) *testContext { dss := []*datasources.DataSource{ {UID: "gIEkMvIVz", Type: "postgres"}, {UID: "sEx6ZvSVk", Type: "testdata"}, @@ -468,24 +526,55 @@ func setup(t *testing.T) *testContext { sqlStore, cfg := db.InitTestDBWithCfg(t) secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore()) ss := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger")) + fakeDatasourceService := &fakeDatasources.FakeDataSourceService{ DataSources: dss, SimulatePluginFailure: false, } - pCtxProvider := plugincontext.ProvideService(cfg, - localcache.ProvideService(), &pluginstore.FakePluginStore{ + pCtxProvider := plugincontext.ProvideService( + cfg, + localcache.ProvideService(), + &pluginstore.FakePluginStore{ PluginList: []pluginstore.Plugin{ {JSONData: plugins.JSONData{ID: "postgres"}}, {JSONData: plugins.JSONData{ID: "testdata"}}, {JSONData: plugins.JSONData{ID: "mysql"}}, }, - }, &fakeDatasources.FakeCacheService{}, fakeDatasourceService, - pluginSettings.ProvideService(sqlStore, secretsService), pluginconfig.NewFakePluginRequestConfigProvider(), + }, + &fakeDatasources.FakeCacheService{}, + fakeDatasourceService, + pluginSettings.ProvideService(sqlStore, secretsService), + pluginconfig.NewFakePluginRequestConfigProvider(), + ) + + var mtdsClientBuilder mtdsclient.MTDatasourceClientBuilder + if isMultiTenant { + mtdsClientBuilder = mtdsclient.NewTestMTDSClientBuilder(isMultiTenant, mockClient) + } else { + mtdsClientBuilder = mtdsclient.NewTestMTDSClientBuilder(false, nil) + } + + exprService := expr.ProvideService( + &setting.Cfg{ExpressionsEnabled: true}, + pc, + pCtxProvider, + featuremgmt.WithFeatures(), + nil, + tracing.InitializeTracerForTest(), + mtdsClientBuilder, ) - exprService := expr.ProvideService(&setting.Cfg{ExpressionsEnabled: true}, pc, pCtxProvider, - featuremgmt.WithFeatures(), nil, tracing.InitializeTracerForTest(), mtdsclient.NewNullMTDatasourceClientBuilder()) - queryService := ProvideService(setting.NewCfg(), dc, exprService, rv, pc, pCtxProvider, mtdsclient.NewNullMTDatasourceClientBuilder()) // provider belonging to this package + + queryService := ProvideService( + setting.NewCfg(), + dc, + exprService, + rv, + pc, + pCtxProvider, + mtdsClientBuilder, + ) + return &testContext{ pluginContext: pc, secretStore: ss, @@ -573,3 +662,20 @@ func (c *fakePluginClient) QueryData(ctx context.Context, req *backend.QueryData return &backend.QueryDataResponse{Responses: make(backend.Responses)}, nil } + +type testClient struct { + queryDataLastCalledWith data.QueryDataRequest + queryDataStubbedResponse *backend.QueryDataResponse + queryDataStubbedError error +} + +func (c *testClient) QueryData(ctx context.Context, req data.QueryDataRequest) (*backend.QueryDataResponse, error) { + c.queryDataLastCalledWith = req + if c.queryDataStubbedError != nil { + return nil, c.queryDataStubbedError + } + if c.queryDataStubbedResponse != nil { + return c.queryDataStubbedResponse, nil + } + return nil, errors.New("no response stubbed") +}