mirror of
https://github.com/grafana/grafana.git
synced 2025-02-11 08:05:43 -06:00
* Disable default golangci-lint filter Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Chore: Fix linter warnings Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
236 lines
6.4 KiB
Go
236 lines
6.4 KiB
Go
package azuremonitor
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"path"
|
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
|
"github.com/grafana/grafana/pkg/api/pluginproxy"
|
|
"github.com/grafana/grafana/pkg/models"
|
|
"github.com/grafana/grafana/pkg/plugins"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
"github.com/grafana/grafana/pkg/tsdb"
|
|
"github.com/grafana/grafana/pkg/util/errutil"
|
|
"github.com/opentracing/opentracing-go"
|
|
"golang.org/x/net/context/ctxhttp"
|
|
)
|
|
|
|
type InsightsAnalyticsDatasource struct {
|
|
httpClient *http.Client
|
|
dsInfo *models.DataSource
|
|
}
|
|
|
|
type InsightsAnalyticsQuery struct {
|
|
RefID string
|
|
|
|
RawQuery string
|
|
InterpolatedQuery string
|
|
|
|
ResultFormat string
|
|
|
|
Params url.Values
|
|
Target string
|
|
}
|
|
|
|
func (e *InsightsAnalyticsDatasource) executeTimeSeriesQuery(ctx context.Context, originalQueries []*tsdb.Query, timeRange *tsdb.TimeRange) (*tsdb.Response, error) {
|
|
result := &tsdb.Response{
|
|
Results: map[string]*tsdb.QueryResult{},
|
|
}
|
|
|
|
queries, err := e.buildQueries(originalQueries, timeRange)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, query := range queries {
|
|
result.Results[query.RefID] = e.executeQuery(ctx, query)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (e *InsightsAnalyticsDatasource) buildQueries(queries []*tsdb.Query, timeRange *tsdb.TimeRange) ([]*InsightsAnalyticsQuery, error) {
|
|
iaQueries := []*InsightsAnalyticsQuery{}
|
|
|
|
for _, query := range queries {
|
|
queryBytes, err := query.Model.Encode()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to re-encode the Azure Application Insights Analytics query into JSON: %w", err)
|
|
}
|
|
|
|
qm := InsightsAnalyticsQuery{}
|
|
queryJSONModel := insightsAnalyticsJSONQuery{}
|
|
err = json.Unmarshal(queryBytes, &queryJSONModel)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode the Azure Application Insights Analytics query object from JSON: %w", err)
|
|
}
|
|
|
|
qm.RawQuery = queryJSONModel.InsightsAnalytics.Query
|
|
qm.ResultFormat = queryJSONModel.InsightsAnalytics.ResultFormat
|
|
qm.RefID = query.RefId
|
|
|
|
if qm.RawQuery == "" {
|
|
return nil, fmt.Errorf("query is missing query string property")
|
|
}
|
|
|
|
qm.InterpolatedQuery, err = KqlInterpolate(query, timeRange, qm.RawQuery)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
qm.Params = url.Values{}
|
|
qm.Params.Add("query", qm.InterpolatedQuery)
|
|
|
|
qm.Target = qm.Params.Encode()
|
|
iaQueries = append(iaQueries, &qm)
|
|
}
|
|
|
|
return iaQueries, nil
|
|
}
|
|
|
|
func (e *InsightsAnalyticsDatasource) executeQuery(ctx context.Context, query *InsightsAnalyticsQuery) *tsdb.QueryResult {
|
|
queryResult := &tsdb.QueryResult{RefId: query.RefID}
|
|
|
|
queryResultError := func(err error) *tsdb.QueryResult {
|
|
queryResult.Error = err
|
|
return queryResult
|
|
}
|
|
|
|
req, err := e.createRequest(ctx, e.dsInfo)
|
|
if err != nil {
|
|
return queryResultError(err)
|
|
}
|
|
req.URL.Path = path.Join(req.URL.Path, "query")
|
|
req.URL.RawQuery = query.Params.Encode()
|
|
|
|
span, ctx := opentracing.StartSpanFromContext(ctx, "application insights analytics query")
|
|
span.SetTag("target", query.Target)
|
|
span.SetTag("datasource_id", e.dsInfo.Id)
|
|
span.SetTag("org_id", e.dsInfo.OrgId)
|
|
|
|
defer span.Finish()
|
|
|
|
err = opentracing.GlobalTracer().Inject(
|
|
span.Context(),
|
|
opentracing.HTTPHeaders,
|
|
opentracing.HTTPHeadersCarrier(req.Header))
|
|
|
|
if err != nil {
|
|
azlog.Warn("failed to inject global tracer")
|
|
}
|
|
|
|
azlog.Debug("ApplicationInsights", "Request URL", req.URL.String())
|
|
res, err := ctxhttp.Do(ctx, e.httpClient, req)
|
|
if err != nil {
|
|
return queryResultError(err)
|
|
}
|
|
|
|
body, err := ioutil.ReadAll(res.Body)
|
|
if err != nil {
|
|
return queryResultError(err)
|
|
}
|
|
defer func() {
|
|
if err := res.Body.Close(); err != nil {
|
|
azlog.Warn("Failed to close response body", "err", err)
|
|
}
|
|
}()
|
|
|
|
if res.StatusCode/100 != 2 {
|
|
azlog.Debug("Request failed", "status", res.Status, "body", string(body))
|
|
return queryResultError(fmt.Errorf("request failed, status: %s, body: %s", res.Status, body))
|
|
}
|
|
var logResponse AzureLogAnalyticsResponse
|
|
d := json.NewDecoder(bytes.NewReader(body))
|
|
d.UseNumber()
|
|
err = d.Decode(&logResponse)
|
|
if err != nil {
|
|
return queryResultError(err)
|
|
}
|
|
|
|
t, err := logResponse.GetPrimaryResultTable()
|
|
if err != nil {
|
|
return queryResultError(err)
|
|
}
|
|
|
|
frame, err := LogTableToFrame(t)
|
|
if err != nil {
|
|
return queryResultError(err)
|
|
}
|
|
|
|
if query.ResultFormat == "time_series" {
|
|
tsSchema := frame.TimeSeriesSchema()
|
|
if tsSchema.Type == data.TimeSeriesTypeLong {
|
|
wideFrame, err := data.LongToWide(frame, nil)
|
|
if err == nil {
|
|
frame = wideFrame
|
|
} else {
|
|
frame.AppendNotices(data.Notice{Severity: data.NoticeSeverityWarning, Text: "could not convert frame to time series, returning raw table: " + err.Error()})
|
|
}
|
|
}
|
|
}
|
|
frames := data.Frames{frame}
|
|
queryResult.Dataframes = tsdb.NewDecodedDataFrames(frames)
|
|
|
|
return queryResult
|
|
}
|
|
|
|
func (e *InsightsAnalyticsDatasource) createRequest(ctx context.Context, dsInfo *models.DataSource) (*http.Request, error) {
|
|
// find plugin
|
|
plugin, ok := plugins.DataSources[dsInfo.Type]
|
|
if !ok {
|
|
return nil, errors.New("unable to find datasource plugin Azure Application Insights")
|
|
}
|
|
|
|
cloudName := dsInfo.JsonData.Get("cloudName").MustString("azuremonitor")
|
|
appInsightsRoute, pluginRouteName, err := e.getPluginRoute(plugin, cloudName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
appInsightsAppID := dsInfo.JsonData.Get("appInsightsAppId").MustString()
|
|
proxyPass := fmt.Sprintf("%s/v1/apps/%s", pluginRouteName, appInsightsAppID)
|
|
|
|
u, err := url.Parse(dsInfo.Url)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to parse url for Application Insights Analytics datasource: %w", err)
|
|
}
|
|
u.Path = path.Join(u.Path, fmt.Sprintf("/v1/apps/%s", appInsightsAppID))
|
|
|
|
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
|
if err != nil {
|
|
azlog.Debug("Failed to create request", "error", err)
|
|
return nil, errutil.Wrap("Failed to create request", err)
|
|
}
|
|
|
|
req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion))
|
|
|
|
pluginproxy.ApplyRoute(ctx, req, proxyPass, appInsightsRoute, dsInfo)
|
|
|
|
return req, nil
|
|
}
|
|
|
|
func (e *InsightsAnalyticsDatasource) getPluginRoute(plugin *plugins.DataSourcePlugin, cloudName string) (*plugins.AppPluginRoute, string, error) {
|
|
pluginRouteName := "appinsights"
|
|
|
|
if cloudName == "chinaazuremonitor" {
|
|
pluginRouteName = "chinaappinsights"
|
|
}
|
|
|
|
var pluginRoute *plugins.AppPluginRoute
|
|
|
|
for _, route := range plugin.Routes {
|
|
if route.Path == pluginRouteName {
|
|
pluginRoute = route
|
|
break
|
|
}
|
|
}
|
|
|
|
return pluginRoute, pluginRouteName, nil
|
|
}
|