| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | package query | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2023-07-06 22:15:43 +08:00
										 |  |  | 	"net/http" | 
					
						
							|  |  |  | 	"runtime" | 
					
						
							| 
									
										
										
										
											2023-11-02 00:17:38 +08:00
										 |  |  | 	"slices" | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-02 02:08:36 +08:00
										 |  |  | 	"github.com/grafana/grafana-plugin-sdk-go/backend" | 
					
						
							| 
									
										
										
										
											2023-01-24 04:10:14 +08:00
										 |  |  | 	"golang.org/x/sync/errgroup" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/api/dtos" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/components/simplejson" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/expr" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/infra/log" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/plugins" | 
					
						
							| 
									
										
										
										
											2023-08-30 22:51:18 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/auth/identity" | 
					
						
							| 
									
										
										
										
											2023-07-06 22:15:43 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/contexthandler" | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/datasources" | 
					
						
							| 
									
										
										
										
											2023-06-08 19:59:51 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" | 
					
						
							| 
									
										
										
										
											2023-01-24 04:10:14 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/validations" | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/setting" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/tsdb/grafanads" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/tsdb/legacydata" | 
					
						
							| 
									
										
										
										
											2022-09-15 00:19:57 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/util/errutil" | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-15 08:35:50 +08:00
										 |  |  | const ( | 
					
						
							| 
									
										
										
										
											2023-03-31 05:40:01 +08:00
										 |  |  | 	HeaderPluginID       = "X-Plugin-Id"         // can be used for routing
 | 
					
						
							|  |  |  | 	HeaderDatasourceUID  = "X-Datasource-Uid"    // can be used for routing/ load balancing
 | 
					
						
							| 
									
										
										
										
											2023-06-22 16:43:38 +08:00
										 |  |  | 	HeaderDashboardUID   = "X-Dashboard-Uid"     // mainly useful for debugging slow queries
 | 
					
						
							|  |  |  | 	HeaderPanelID        = "X-Panel-Id"          // mainly useful for debugging slow queries
 | 
					
						
							| 
									
										
										
										
											2023-03-31 05:40:01 +08:00
										 |  |  | 	HeaderQueryGroupID   = "X-Query-Group-Id"    // mainly useful for finding related queries with query chunking
 | 
					
						
							|  |  |  | 	HeaderFromExpression = "X-Grafana-From-Expr" // used by datasources to identify expression queries
 | 
					
						
							| 
									
										
										
										
											2022-11-15 08:35:50 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-08 03:45:26 +08:00
										 |  |  | func ProvideService( | 
					
						
							|  |  |  | 	cfg *setting.Cfg, | 
					
						
							|  |  |  | 	dataSourceCache datasources.CacheService, | 
					
						
							|  |  |  | 	expressionService *expr.Service, | 
					
						
							| 
									
										
										
										
											2023-01-24 04:10:14 +08:00
										 |  |  | 	pluginRequestValidator validations.PluginRequestValidator, | 
					
						
							| 
									
										
										
										
											2022-01-08 03:45:26 +08:00
										 |  |  | 	pluginClient plugins.Client, | 
					
						
							| 
									
										
										
										
											2023-06-08 19:59:51 +08:00
										 |  |  | 	pCtxProvider *plugincontext.Provider, | 
					
						
							| 
									
										
										
										
											2023-03-17 02:39:17 +08:00
										 |  |  | ) *ServiceImpl { | 
					
						
							|  |  |  | 	g := &ServiceImpl{ | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 		cfg:                    cfg, | 
					
						
							|  |  |  | 		dataSourceCache:        dataSourceCache, | 
					
						
							|  |  |  | 		expressionService:      expressionService, | 
					
						
							|  |  |  | 		pluginRequestValidator: pluginRequestValidator, | 
					
						
							|  |  |  | 		pluginClient:           pluginClient, | 
					
						
							| 
									
										
										
										
											2023-06-08 19:59:51 +08:00
										 |  |  | 		pCtxProvider:           pCtxProvider, | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 		log:                    log.New("query_data"), | 
					
						
							| 
									
										
										
										
											2023-07-06 22:15:43 +08:00
										 |  |  | 		concurrentQueryLimit:   cfg.SectionWithEnvOverrides("query").Key("concurrent_query_limit").MustInt(runtime.NumCPU()), | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	g.log.Info("Query Service initialization") | 
					
						
							|  |  |  | 	return g | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-17 02:39:17 +08:00
										 |  |  | //go:generate mockery --name Service --structname FakeQueryService --inpackage --filename query_service_mock.go
 | 
					
						
							|  |  |  | type Service interface { | 
					
						
							|  |  |  | 	Run(ctx context.Context) error | 
					
						
							| 
									
										
										
										
											2023-08-30 22:51:18 +08:00
										 |  |  | 	QueryData(ctx context.Context, user identity.Requester, skipDSCache bool, reqDTO dtos.MetricRequest) (*backend.QueryDataResponse, error) | 
					
						
							| 
									
										
										
										
											2023-03-17 02:39:17 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Gives us compile time error if the service does not adhere to the contract of the interface
 | 
					
						
							|  |  |  | var _ Service = (*ServiceImpl)(nil) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type ServiceImpl struct { | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 	cfg                    *setting.Cfg | 
					
						
							|  |  |  | 	dataSourceCache        datasources.CacheService | 
					
						
							|  |  |  | 	expressionService      *expr.Service | 
					
						
							| 
									
										
										
										
											2023-01-24 04:10:14 +08:00
										 |  |  | 	pluginRequestValidator validations.PluginRequestValidator | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 	pluginClient           plugins.Client | 
					
						
							| 
									
										
										
										
											2023-06-08 19:59:51 +08:00
										 |  |  | 	pCtxProvider           *plugincontext.Provider | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 	log                    log.Logger | 
					
						
							| 
									
										
										
										
											2023-07-06 22:15:43 +08:00
										 |  |  | 	concurrentQueryLimit   int | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-17 02:39:17 +08:00
										 |  |  | // Run ServiceImpl.
 | 
					
						
							|  |  |  | func (s *ServiceImpl) Run(ctx context.Context) error { | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 	<-ctx.Done() | 
					
						
							|  |  |  | 	return ctx.Err() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-14 22:27:06 +08:00
										 |  |  | // QueryData processes queries and returns query responses. It handles queries to single or mixed datasources, as well as expressions.
 | 
					
						
							| 
									
										
										
										
											2023-08-30 22:51:18 +08:00
										 |  |  | func (s *ServiceImpl) QueryData(ctx context.Context, user identity.Requester, skipDSCache bool, reqDTO dtos.MetricRequest) (*backend.QueryDataResponse, error) { | 
					
						
							| 
									
										
										
										
											2022-10-14 22:27:06 +08:00
										 |  |  | 	// Parse the request into parsed queries grouped by datasource uid
 | 
					
						
							| 
									
										
										
										
											2023-04-13 00:30:33 +08:00
										 |  |  | 	parsedReq, err := s.parseMetricRequest(ctx, user, skipDSCache, reqDTO) | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-11-15 08:35:50 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-14 22:27:06 +08:00
										 |  |  | 	// If there are expressions, handle them and return
 | 
					
						
							|  |  |  | 	if parsedReq.hasExpression { | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 		return s.handleExpressions(ctx, user, parsedReq) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-10-14 22:27:06 +08:00
										 |  |  | 	// If there is only one datasource, query it and return
 | 
					
						
							|  |  |  | 	if len(parsedReq.parsedQueries) == 1 { | 
					
						
							|  |  |  | 		return s.handleQuerySingleDatasource(ctx, user, parsedReq) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// If there are multiple datasources, handle their queries concurrently and return the aggregate result
 | 
					
						
							| 
									
										
										
										
											2023-04-13 00:30:33 +08:00
										 |  |  | 	return s.executeConcurrentQueries(ctx, user, skipDSCache, reqDTO, parsedReq.parsedQueries) | 
					
						
							| 
									
										
										
										
											2022-11-28 23:21:54 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2022-06-14 07:23:56 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-06 22:15:43 +08:00
										 |  |  | // splitResponse contains the results of a concurrent data source query - the response and any headers
 | 
					
						
							|  |  |  | type splitResponse struct { | 
					
						
							|  |  |  | 	responses backend.Responses | 
					
						
							|  |  |  | 	header    http.Header | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-28 23:21:54 +08:00
										 |  |  | // executeConcurrentQueries executes queries to multiple datasources concurrently and returns the aggregate result.
 | 
					
						
							| 
									
										
										
										
											2023-08-30 22:51:18 +08:00
										 |  |  | func (s *ServiceImpl) executeConcurrentQueries(ctx context.Context, user identity.Requester, skipDSCache bool, reqDTO dtos.MetricRequest, queriesbyDs map[string][]parsedQuery) (*backend.QueryDataResponse, error) { | 
					
						
							| 
									
										
										
										
											2022-10-14 22:27:06 +08:00
										 |  |  | 	g, ctx := errgroup.WithContext(ctx) | 
					
						
							| 
									
										
										
										
											2023-07-06 22:15:43 +08:00
										 |  |  | 	g.SetLimit(s.concurrentQueryLimit) // prevent too many concurrent requests
 | 
					
						
							|  |  |  | 	rchan := make(chan splitResponse, len(queriesbyDs)) | 
					
						
							| 
									
										
										
										
											2022-11-28 23:21:54 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Create panic recovery function for loop below
 | 
					
						
							|  |  |  | 	recoveryFn := func(queries []*simplejson.Json) { | 
					
						
							|  |  |  | 		if r := recover(); r != nil { | 
					
						
							|  |  |  | 			var err error | 
					
						
							|  |  |  | 			s.log.Error("query datasource panic", "error", r, "stack", log.Stack(1)) | 
					
						
							|  |  |  | 			if theErr, ok := r.(error); ok { | 
					
						
							|  |  |  | 				err = theErr | 
					
						
							|  |  |  | 			} else if theErrString, ok := r.(string); ok { | 
					
						
							|  |  |  | 				err = fmt.Errorf(theErrString) | 
					
						
							|  |  |  | 			} else { | 
					
						
							| 
									
										
										
										
											2023-06-16 23:46:47 +08:00
										 |  |  | 				err = fmt.Errorf("unexpected error - %s", s.cfg.UserFacingDefaultError) | 
					
						
							| 
									
										
										
										
											2022-11-28 23:21:54 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 			// Due to the panic, there is no valid response for any query for this datasource. Append an error for each one.
 | 
					
						
							|  |  |  | 			rchan <- buildErrorResponses(err, queries) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-10-06 05:45:17 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-28 23:21:54 +08:00
										 |  |  | 	// Query each datasource concurrently
 | 
					
						
							|  |  |  | 	for _, queries := range queriesbyDs { | 
					
						
							| 
									
										
										
										
											2022-10-14 22:27:06 +08:00
										 |  |  | 		rawQueries := make([]*simplejson.Json, len(queries)) | 
					
						
							|  |  |  | 		for i := 0; i < len(queries); i++ { | 
					
						
							|  |  |  | 			rawQueries[i] = queries[i].rawQuery | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		g.Go(func() error { | 
					
						
							|  |  |  | 			subDTO := reqDTO.CloneWithQueries(rawQueries) | 
					
						
							| 
									
										
										
										
											2022-11-28 23:21:54 +08:00
										 |  |  | 			// Handle panics in the datasource qery
 | 
					
						
							|  |  |  | 			defer recoveryFn(subDTO.Queries) | 
					
						
							| 
									
										
										
										
											2022-06-14 07:23:56 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-06 22:15:43 +08:00
										 |  |  | 			ctxCopy := contexthandler.CopyWithReqContext(ctx) | 
					
						
							|  |  |  | 			subResp, err := s.QueryData(ctxCopy, user, skipDSCache, subDTO) | 
					
						
							| 
									
										
										
										
											2022-10-14 22:27:06 +08:00
										 |  |  | 			if err == nil { | 
					
						
							| 
									
										
										
										
											2023-07-06 22:15:43 +08:00
										 |  |  | 				reqCtx, header := contexthandler.FromContext(ctxCopy), http.Header{} | 
					
						
							|  |  |  | 				if reqCtx != nil { | 
					
						
							|  |  |  | 					header = reqCtx.Resp.Header() | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				rchan <- splitResponse{subResp.Responses, header} | 
					
						
							| 
									
										
										
										
											2022-11-28 23:21:54 +08:00
										 |  |  | 			} else { | 
					
						
							|  |  |  | 				// If there was an error, return an error response for each query for this datasource
 | 
					
						
							|  |  |  | 				rchan <- buildErrorResponses(err, subDTO.Queries) | 
					
						
							| 
									
										
										
										
											2022-10-14 22:27:06 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-11-28 23:21:54 +08:00
										 |  |  | 			return nil | 
					
						
							| 
									
										
										
										
											2022-10-14 22:27:06 +08:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-10-06 05:45:17 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-14 22:27:06 +08:00
										 |  |  | 	if err := g.Wait(); err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-11-28 23:21:54 +08:00
										 |  |  | 	close(rchan) | 
					
						
							|  |  |  | 	resp := backend.NewQueryDataResponse() | 
					
						
							| 
									
										
										
										
											2023-07-06 22:15:43 +08:00
										 |  |  | 	reqCtx := contexthandler.FromContext(ctx) | 
					
						
							| 
									
										
										
										
											2022-11-28 23:21:54 +08:00
										 |  |  | 	for result := range rchan { | 
					
						
							| 
									
										
										
										
											2023-07-06 22:15:43 +08:00
										 |  |  | 		for refId, dataResponse := range result.responses { | 
					
						
							| 
									
										
										
										
											2022-10-14 22:27:06 +08:00
										 |  |  | 			resp.Responses[refId] = dataResponse | 
					
						
							| 
									
										
										
										
											2022-06-14 07:23:56 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-07-06 22:15:43 +08:00
										 |  |  | 		if reqCtx != nil { | 
					
						
							|  |  |  | 			for k, v := range result.header { | 
					
						
							|  |  |  | 				for _, val := range v { | 
					
						
							|  |  |  | 					if !slices.Contains(reqCtx.Resp.Header().Values(k), val) { | 
					
						
							|  |  |  | 						reqCtx.Resp.Header().Add(k, val) | 
					
						
							|  |  |  | 					} else { | 
					
						
							|  |  |  | 						s.log.Warn("skipped duplicate response header", "header", k, "value", val) | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-06-14 07:23:56 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-10-14 22:27:06 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return resp, nil | 
					
						
							| 
									
										
										
										
											2022-06-14 07:23:56 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-28 23:21:54 +08:00
										 |  |  | // buildErrorResponses applies the provided error to each query response in the list. These queries should all belong to the same datasource.
 | 
					
						
							| 
									
										
										
										
											2023-07-06 22:15:43 +08:00
										 |  |  | func buildErrorResponses(err error, queries []*simplejson.Json) splitResponse { | 
					
						
							| 
									
										
										
										
											2022-11-28 23:21:54 +08:00
										 |  |  | 	er := backend.Responses{} | 
					
						
							|  |  |  | 	for _, query := range queries { | 
					
						
							|  |  |  | 		er[query.Get("refId").MustString("A")] = backend.DataResponse{ | 
					
						
							|  |  |  | 			Error: err, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-07-06 22:15:43 +08:00
										 |  |  | 	return splitResponse{er, http.Header{}} | 
					
						
							| 
									
										
										
										
											2022-11-28 23:21:54 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | // handleExpressions handles POST /api/ds/query when there is an expression.
 | 
					
						
							| 
									
										
										
										
											2023-08-30 22:51:18 +08:00
										 |  |  | func (s *ServiceImpl) handleExpressions(ctx context.Context, user identity.Requester, parsedReq *parsedRequest) (*backend.QueryDataResponse, error) { | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 	exprReq := expr.Request{ | 
					
						
							|  |  |  | 		Queries: []expr.Query{}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-28 20:40:06 +08:00
										 |  |  | 	if user != nil { // for passthrough authentication, SSE does not authenticate
 | 
					
						
							| 
									
										
										
										
											2023-06-08 19:59:51 +08:00
										 |  |  | 		exprReq.User = user | 
					
						
							| 
									
										
										
										
											2023-08-30 22:51:18 +08:00
										 |  |  | 		exprReq.OrgId = user.GetOrgID() | 
					
						
							| 
									
										
										
										
											2022-11-28 20:40:06 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-14 22:27:06 +08:00
										 |  |  | 	for _, pq := range parsedReq.getFlattenedQueries() { | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 		if pq.datasource == nil { | 
					
						
							| 
									
										
										
										
											2022-09-15 00:19:57 +08:00
										 |  |  | 			return nil, ErrMissingDataSourceInfo.Build(errutil.TemplateData{ | 
					
						
							| 
									
										
										
										
											2023-08-30 23:46:47 +08:00
										 |  |  | 				Public: map[string]any{ | 
					
						
							| 
									
										
										
										
											2022-09-15 00:19:57 +08:00
										 |  |  | 					"RefId": pq.query.RefID, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}) | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		exprReq.Queries = append(exprReq.Queries, expr.Query{ | 
					
						
							|  |  |  | 			JSON:          pq.query.JSON, | 
					
						
							|  |  |  | 			Interval:      pq.query.Interval, | 
					
						
							|  |  |  | 			RefID:         pq.query.RefID, | 
					
						
							|  |  |  | 			MaxDataPoints: pq.query.MaxDataPoints, | 
					
						
							|  |  |  | 			QueryType:     pq.query.QueryType, | 
					
						
							| 
									
										
										
										
											2021-12-17 00:51:46 +08:00
										 |  |  | 			DataSource:    pq.datasource, | 
					
						
							| 
									
										
										
										
											2022-10-27 04:13:58 +08:00
										 |  |  | 			TimeRange: expr.AbsoluteTimeRange{ | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 				From: pq.query.TimeRange.From, | 
					
						
							|  |  |  | 				To:   pq.query.TimeRange.To, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-27 04:13:58 +08:00
										 |  |  | 	qdr, err := s.expressionService.TransformData(ctx, time.Now(), &exprReq) // use time now because all queries have absolute time range
 | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("expression request error: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return qdr, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-14 22:27:06 +08:00
										 |  |  | // handleQuerySingleDatasource handles one or more queries to a single datasource
 | 
					
						
							| 
									
										
										
										
											2023-08-30 22:51:18 +08:00
										 |  |  | func (s *ServiceImpl) handleQuerySingleDatasource(ctx context.Context, user identity.Requester, parsedReq *parsedRequest) (*backend.QueryDataResponse, error) { | 
					
						
							| 
									
										
										
										
											2022-10-14 22:27:06 +08:00
										 |  |  | 	queries := parsedReq.getFlattenedQueries() | 
					
						
							|  |  |  | 	ds := queries[0].datasource | 
					
						
							| 
									
										
										
										
											2023-02-03 00:22:43 +08:00
										 |  |  | 	if err := s.pluginRequestValidator.Validate(ds.URL, nil); err != nil { | 
					
						
							| 
									
										
										
										
											2022-06-28 00:23:15 +08:00
										 |  |  | 		return nil, datasources.ErrDataSourceAccessDenied | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-14 22:27:06 +08:00
										 |  |  | 	// ensure that each query passed to this function has the same datasource
 | 
					
						
							|  |  |  | 	for _, pq := range queries { | 
					
						
							| 
									
										
										
										
											2023-02-03 00:22:43 +08:00
										 |  |  | 		if ds.UID != pq.datasource.UID { | 
					
						
							|  |  |  | 			return nil, fmt.Errorf("all queries must have the same datasource - found %s and %s", ds.UID, pq.datasource.UID) | 
					
						
							| 
									
										
										
										
											2022-10-14 22:27:06 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-08 19:59:51 +08:00
										 |  |  | 	pCtx, err := s.pCtxProvider.GetWithDataSource(ctx, ds.Type, user, ds) | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2022-07-13 21:27:03 +08:00
										 |  |  | 		return nil, err | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	req := &backend.QueryDataRequest{ | 
					
						
							| 
									
										
										
										
											2023-06-08 19:59:51 +08:00
										 |  |  | 		PluginContext: pCtx, | 
					
						
							|  |  |  | 		Headers:       map[string]string{}, | 
					
						
							|  |  |  | 		Queries:       []backend.DataQuery{}, | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-14 22:27:06 +08:00
										 |  |  | 	for _, q := range queries { | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 		req.Queries = append(req.Queries, q.query) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return s.pluginClient.QueryData(ctx, req) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-14 22:27:06 +08:00
										 |  |  | // parseRequest parses a request into parsed queries grouped by datasource uid
 | 
					
						
							| 
									
										
										
										
											2023-08-30 22:51:18 +08:00
										 |  |  | func (s *ServiceImpl) parseMetricRequest(ctx context.Context, user identity.Requester, skipDSCache bool, reqDTO dtos.MetricRequest) (*parsedRequest, error) { | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 	if len(reqDTO.Queries) == 0 { | 
					
						
							| 
									
										
										
										
											2022-09-15 00:19:57 +08:00
										 |  |  | 		return nil, ErrNoQueriesFound | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	timeRange := legacydata.NewDataTimeRange(reqDTO.From, reqDTO.To) | 
					
						
							|  |  |  | 	req := &parsedRequest{ | 
					
						
							|  |  |  | 		hasExpression: false, | 
					
						
							| 
									
										
										
										
											2022-10-14 22:27:06 +08:00
										 |  |  | 		parsedQueries: make(map[string][]parsedQuery), | 
					
						
							| 
									
										
										
										
											2022-11-15 08:35:50 +08:00
										 |  |  | 		dsTypes:       make(map[string]bool), | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-14 22:27:06 +08:00
										 |  |  | 	// Parse the queries and store them by datasource
 | 
					
						
							| 
									
										
										
										
											2022-06-28 00:23:15 +08:00
										 |  |  | 	datasourcesByUid := map[string]*datasources.DataSource{} | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 	for _, query := range reqDTO.Queries { | 
					
						
							| 
									
										
										
										
											2023-04-13 00:30:33 +08:00
										 |  |  | 		ds, err := s.getDataSourceFromQuery(ctx, user, skipDSCache, query, datasourcesByUid) | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if ds == nil { | 
					
						
							| 
									
										
										
										
											2022-09-15 00:19:57 +08:00
										 |  |  | 			return nil, ErrInvalidDatasourceID | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-03 00:22:43 +08:00
										 |  |  | 		datasourcesByUid[ds.UID] = ds | 
					
						
							| 
									
										
										
										
											2023-06-17 01:05:06 +08:00
										 |  |  | 		if expr.NodeTypeFromDatasourceUID(ds.UID) != expr.TypeDatasourceNode { | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 			req.hasExpression = true | 
					
						
							| 
									
										
										
										
											2022-11-15 08:35:50 +08:00
										 |  |  | 		} else { | 
					
						
							|  |  |  | 			req.dsTypes[ds.Type] = true | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-03 00:22:43 +08:00
										 |  |  | 		if _, ok := req.parsedQueries[ds.UID]; !ok { | 
					
						
							|  |  |  | 			req.parsedQueries[ds.UID] = []parsedQuery{} | 
					
						
							| 
									
										
										
										
											2022-10-14 22:27:06 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 		s.log.Debug("Processing metrics query", "query", query) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		modelJSON, err := query.MarshalJSON() | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-03 00:22:43 +08:00
										 |  |  | 		req.parsedQueries[ds.UID] = append(req.parsedQueries[ds.UID], parsedQuery{ | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 			datasource: ds, | 
					
						
							|  |  |  | 			query: backend.DataQuery{ | 
					
						
							|  |  |  | 				TimeRange: backend.TimeRange{ | 
					
						
							|  |  |  | 					From: timeRange.GetFromAsTimeUTC(), | 
					
						
							|  |  |  | 					To:   timeRange.GetToAsTimeUTC(), | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 				RefID:         query.Get("refId").MustString("A"), | 
					
						
							|  |  |  | 				MaxDataPoints: query.Get("maxDataPoints").MustInt64(100), | 
					
						
							|  |  |  | 				Interval:      time.Duration(query.Get("intervalMs").MustInt64(1000)) * time.Millisecond, | 
					
						
							|  |  |  | 				QueryType:     query.Get("queryType").MustString(""), | 
					
						
							|  |  |  | 				JSON:          modelJSON, | 
					
						
							|  |  |  | 			}, | 
					
						
							| 
									
										
										
										
											2022-10-14 22:27:06 +08:00
										 |  |  | 			rawQuery: query, | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-09 17:11:16 +08:00
										 |  |  | 	return req, req.validateRequest(ctx) | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-30 22:51:18 +08:00
										 |  |  | func (s *ServiceImpl) getDataSourceFromQuery(ctx context.Context, user identity.Requester, skipDSCache bool, query *simplejson.Json, history map[string]*datasources.DataSource) (*datasources.DataSource, error) { | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 	var err error | 
					
						
							|  |  |  | 	uid := query.Get("datasource").Get("uid").MustString() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// before 8.3 special types could be sent as datasource (expr)
 | 
					
						
							|  |  |  | 	if uid == "" { | 
					
						
							|  |  |  | 		uid = query.Get("datasource").MustString() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// check cache value
 | 
					
						
							|  |  |  | 	ds, ok := history[uid] | 
					
						
							|  |  |  | 	if ok { | 
					
						
							|  |  |  | 		return ds, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-17 01:05:06 +08:00
										 |  |  | 	if kind := expr.NodeTypeFromDatasourceUID(uid); kind != expr.TypeDatasourceNode { | 
					
						
							|  |  |  | 		return expr.DataSourceModelFromNodeType(kind) | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if uid == grafanads.DatasourceUID { | 
					
						
							| 
									
										
										
										
											2023-08-30 22:51:18 +08:00
										 |  |  | 		return grafanads.DataSourceModel(user.GetOrgID()), nil | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-02 22:43:07 +08:00
										 |  |  | 	if uid != "" { | 
					
						
							| 
									
										
										
										
											2023-04-13 00:30:33 +08:00
										 |  |  | 		ds, err = s.dataSourceCache.GetDatasourceByUID(ctx, uid, user, skipDSCache) | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return ds, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-02 22:43:07 +08:00
										 |  |  | 	// use datasourceId if it exists
 | 
					
						
							|  |  |  | 	id := query.Get("datasourceId").MustInt64(0) | 
					
						
							|  |  |  | 	if id > 0 { | 
					
						
							| 
									
										
										
										
											2023-04-13 00:30:33 +08:00
										 |  |  | 		ds, err = s.dataSourceCache.GetDatasource(ctx, id, user, skipDSCache) | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return ds, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-15 00:19:57 +08:00
										 |  |  | 	return nil, ErrInvalidDatasourceID | 
					
						
							| 
									
										
										
										
											2021-12-15 01:38:20 +08:00
										 |  |  | } |