Update azure monitor

This commit is contained in:
Ivana Huckova 2024-11-28 12:36:53 +01:00
parent 348c8fa927
commit 21b53845b8
5 changed files with 88 additions and 22 deletions

View File

@ -18,7 +18,6 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/tracing" "github.com/grafana/grafana-plugin-sdk-go/backend/tracing"
"github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana-plugin-sdk-go/experimental/errorsource"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
@ -142,12 +141,12 @@ func (e *AzureLogAnalyticsDatasource) ExecuteTimeSeriesQuery(ctx context.Context
for _, query := range originalQueries { for _, query := range originalQueries {
logsQuery, err := e.buildQuery(ctx, query, dsInfo, fromAlert) logsQuery, err := e.buildQuery(ctx, query, dsInfo, fromAlert)
if err != nil { if err != nil {
errorsource.AddErrorToResponse(query.RefID, result, err) result.Responses[query.RefID] = backend.ErrorResponseWithErrorSource(err)
continue continue
} }
res, err := e.executeQuery(ctx, logsQuery, dsInfo, client, url) res, err := e.executeQuery(ctx, logsQuery, dsInfo, client, url)
if err != nil { if err != nil {
errorsource.AddErrorToResponse(query.RefID, result, err) result.Responses[query.RefID] = backend.ErrorResponseWithErrorSource(err)
continue continue
} }
result.Responses[query.RefID] = *res result.Responses[query.RefID] = *res
@ -252,7 +251,7 @@ func (e *AzureLogAnalyticsDatasource) buildQuery(ctx context.Context, query back
cfg := backend.GrafanaConfigFromContext(ctx) cfg := backend.GrafanaConfigFromContext(ctx)
hasPromExemplarsToggle := cfg.FeatureToggles().IsEnabled("azureMonitorPrometheusExemplars") hasPromExemplarsToggle := cfg.FeatureToggles().IsEnabled("azureMonitorPrometheusExemplars")
if !hasPromExemplarsToggle { if !hasPromExemplarsToggle {
return nil, errorsource.DownstreamError(fmt.Errorf("query type unsupported as azureMonitorPrometheusExemplars feature toggle is not enabled"), false) return nil, backend.DownstreamError(fmt.Errorf("query type unsupported as azureMonitorPrometheusExemplars feature toggle is not enabled"))
} }
} }
azureAppInsightsQuery, err := buildAppInsightsQuery(ctx, query, dsInfo, appInsightsRegExp, e.Logger) azureAppInsightsQuery, err := buildAppInsightsQuery(ctx, query, dsInfo, appInsightsRegExp, e.Logger)
@ -269,7 +268,7 @@ func (e *AzureLogAnalyticsDatasource) buildQuery(ctx context.Context, query back
func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, query *AzureLogAnalyticsQuery, dsInfo types.DatasourceInfo, client *http.Client, url string) (*backend.DataResponse, error) { func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, query *AzureLogAnalyticsQuery, dsInfo types.DatasourceInfo, client *http.Client, url string) (*backend.DataResponse, error) {
// If azureLogAnalyticsSameAs is defined and set to false, return an error // If azureLogAnalyticsSameAs is defined and set to false, return an error
if sameAs, ok := dsInfo.JSONData["azureLogAnalyticsSameAs"]; ok && !sameAs.(bool) { if sameAs, ok := dsInfo.JSONData["azureLogAnalyticsSameAs"]; ok && !sameAs.(bool) {
return nil, errorsource.DownstreamError(fmt.Errorf("credentials for Log Analytics are no longer supported. Go to the data source configuration to update Azure Monitor credentials"), false) return nil, backend.DownstreamError(fmt.Errorf("credentials for Log Analytics are no longer supported. Go to the data source configuration to update Azure Monitor credentials"))
} }
queryJSONModel := dataquery.AzureMonitorQuery{} queryJSONModel := dataquery.AzureMonitorQuery{}
@ -280,7 +279,7 @@ func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, query *A
if query.QueryType == dataquery.AzureQueryTypeAzureTraces { if query.QueryType == dataquery.AzureQueryTypeAzureTraces {
if query.ResultFormat == dataquery.ResultFormatTrace && query.Query == "" { if query.ResultFormat == dataquery.ResultFormatTrace && query.Query == "" {
return nil, errorsource.DownstreamError(fmt.Errorf("cannot visualise trace events using the trace visualiser"), false) return nil, backend.DownstreamError(fmt.Errorf("cannot visualise trace events using the trace visualiser"))
} }
} }
@ -301,7 +300,7 @@ func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, query *A
res, err := client.Do(req) res, err := client.Do(req)
if err != nil { if err != nil {
return nil, errorsource.DownstreamError(err, false) return nil, backend.DownstreamError(err)
} }
defer func() { defer func() {
@ -597,11 +596,11 @@ func getCorrelationWorkspaces(ctx context.Context, baseResource string, resource
res, err := azMonService.HTTPClient.Do(req) res, err := azMonService.HTTPClient.Do(req)
if err != nil { if err != nil {
return AzureCorrelationAPIResponse{}, errorsource.DownstreamError(err, false) return AzureCorrelationAPIResponse{}, backend.DownstreamError(err)
} }
body, err := io.ReadAll(res.Body) body, err := io.ReadAll(res.Body)
if err != nil { if err != nil {
return AzureCorrelationAPIResponse{}, errorsource.DownstreamError(err, false) return AzureCorrelationAPIResponse{}, backend.DownstreamError(err)
} }
defer func() { defer func() {
@ -611,7 +610,7 @@ func getCorrelationWorkspaces(ctx context.Context, baseResource string, resource
}() }()
if res.StatusCode/100 != 2 { if res.StatusCode/100 != 2 {
return AzureCorrelationAPIResponse{}, errorsource.SourceError(backend.ErrorSourceFromHTTPStatus(res.StatusCode), fmt.Errorf("request failed, status: %s, body: %s", res.Status, string(body)), false) return AzureCorrelationAPIResponse{}, utils.CreateResponseErrorFromStatusCode(res.StatusCode, res.Status, body)
} }
var data AzureCorrelationAPIResponse var data AzureCorrelationAPIResponse
d := json.NewDecoder(bytes.NewReader(body)) d := json.NewDecoder(bytes.NewReader(body))
@ -675,7 +674,7 @@ func (e *AzureLogAnalyticsDatasource) unmarshalResponse(res *http.Response) (Azu
}() }()
if res.StatusCode/100 != 2 { if res.StatusCode/100 != 2 {
return AzureLogAnalyticsResponse{}, errorsource.SourceError(backend.ErrorSourceFromHTTPStatus(res.StatusCode), fmt.Errorf("request failed, status: %s, body: %s", res.Status, string(body)), false) return AzureLogAnalyticsResponse{}, utils.CreateResponseErrorFromStatusCode(res.StatusCode, res.Status, body)
} }
var data AzureLogAnalyticsResponse var data AzureLogAnalyticsResponse

View File

@ -7,8 +7,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana-plugin-sdk-go/experimental/errorsource"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/kinds/dataquery" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/kinds/dataquery"
) )
@ -47,18 +47,18 @@ func AddConfigLinks(frame data.Frame, dl string, title *string) data.Frame {
// 4. the ds toggle is set to true // 4. the ds toggle is set to true
func meetsBasicLogsCriteria(resources []string, fromAlert bool, basicLogsEnabled bool) (bool, error) { func meetsBasicLogsCriteria(resources []string, fromAlert bool, basicLogsEnabled bool) (bool, error) {
if fromAlert { if fromAlert {
return false, errorsource.DownstreamError(fmt.Errorf("basic Logs queries cannot be used for alerts"), false) return false, backend.DownstreamError(fmt.Errorf("basic Logs queries cannot be used for alerts"))
} }
if len(resources) != 1 { if len(resources) != 1 {
return false, errorsource.DownstreamError(fmt.Errorf("basic logs queries cannot be run against multiple resources"), false) return false, backend.DownstreamError(fmt.Errorf("basic logs queries cannot be run against multiple resources"))
} }
if !strings.Contains(strings.ToLower(resources[0]), "microsoft.operationalinsights/workspaces") { if !strings.Contains(strings.ToLower(resources[0]), "microsoft.operationalinsights/workspaces") {
return false, errorsource.DownstreamError(fmt.Errorf("basic logs queries may only be run against Log Analytics workspaces"), false) return false, backend.DownstreamError(fmt.Errorf("basic logs queries may only be run against Log Analytics workspaces"))
} }
if !basicLogsEnabled { if !basicLogsEnabled {
return false, errorsource.DownstreamError(fmt.Errorf("basic Logs queries are disabled for this data source"), false) return false, backend.DownstreamError(fmt.Errorf("basic Logs queries are disabled for this data source"))
} }
return true, nil return true, nil

View File

@ -17,7 +17,6 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/grafana/grafana-plugin-sdk-go/backend/tracing" "github.com/grafana/grafana-plugin-sdk-go/backend/tracing"
"github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana-plugin-sdk-go/experimental/errorsource"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
@ -25,6 +24,7 @@ import (
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/loganalytics" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/loganalytics"
azTime "github.com/grafana/grafana/pkg/tsdb/azuremonitor/time" azTime "github.com/grafana/grafana/pkg/tsdb/azuremonitor/time"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/types" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/types"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/utils"
) )
// AzureMonitorDatasource calls the Azure Monitor API - one of the four API's supported // AzureMonitorDatasource calls the Azure Monitor API - one of the four API's supported
@ -55,12 +55,12 @@ func (e *AzureMonitorDatasource) ExecuteTimeSeriesQuery(ctx context.Context, ori
for _, query := range originalQueries { for _, query := range originalQueries {
azureQuery, err := e.buildQuery(query, dsInfo) azureQuery, err := e.buildQuery(query, dsInfo)
if err != nil { if err != nil {
errorsource.AddErrorToResponse(query.RefID, result, err) result.Responses[query.RefID] = backend.ErrorResponseWithErrorSource(err)
continue continue
} }
res, err := e.executeQuery(ctx, azureQuery, dsInfo, client, url) res, err := e.executeQuery(ctx, azureQuery, dsInfo, client, url)
if err != nil { if err != nil {
errorsource.AddErrorToResponse(query.RefID, result, err) result.Responses[query.RefID] = backend.ErrorResponseWithErrorSource(err)
continue continue
} }
result.Responses[query.RefID] = *res result.Responses[query.RefID] = *res
@ -284,7 +284,7 @@ func (e *AzureMonitorDatasource) retrieveSubscriptionDetails(cli *http.Client, c
} }
if res.StatusCode/100 != 2 { if res.StatusCode/100 != 2 {
return "", errorsource.SourceError(backend.ErrorSourceFromHTTPStatus(res.StatusCode), fmt.Errorf("request failed, status: %s, error: %s", res.Status, string(body)), false) return "", utils.CreateResponseErrorFromStatusCode(res.StatusCode, res.Status, body)
} }
var data types.SubscriptionsResponse var data types.SubscriptionsResponse
@ -321,7 +321,7 @@ func (e *AzureMonitorDatasource) executeQuery(ctx context.Context, query *types.
res, err := cli.Do(req) res, err := cli.Do(req)
if err != nil { if err != nil {
return nil, errorsource.DownstreamError(err, false) return nil, backend.DownstreamError(err)
} }
defer func() { defer func() {
@ -366,7 +366,7 @@ func (e *AzureMonitorDatasource) unmarshalResponse(res *http.Response) (types.Az
} }
if res.StatusCode/100 != 2 { if res.StatusCode/100 != 2 {
return types.AzureMonitorResponse{}, errorsource.SourceError(backend.ErrorSourceFromHTTPStatus(res.StatusCode), fmt.Errorf("request failed, status: %s, body: %s", res.Status, string(body)), false) return types.AzureMonitorResponse{}, utils.CreateResponseErrorFromStatusCode(res.StatusCode, res.Status, body)
} }
var data types.AzureMonitorResponse var data types.AzureMonitorResponse

View File

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/grafana/grafana-plugin-sdk-go/experimental/errorsource" "github.com/grafana/grafana-plugin-sdk-go/experimental/errorsource"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/types" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/types"
@ -78,3 +79,11 @@ func ApplySourceFromError(errorMessage error, err error) error {
} }
return errorMessage return errorMessage
} }
func CreateResponseErrorFromStatusCode(statusCode int, status string, body []byte) error {
statusErr := fmt.Errorf("request failed, status: %s, body: %s", status, string(body))
if backend.ErrorSourceFromHTTPStatus(statusCode) == backend.ErrorSourceDownstream {
return backend.DownstreamError(statusErr)
}
return backend.PluginError(statusErr)
}

View File

@ -0,0 +1,58 @@
package utils
import (
"testing"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/stretchr/testify/assert"
)
func TestCreateResponseErrorFromStatusCode(t *testing.T) {
tests := []struct {
name string
statusCode int
status string
body []byte
expectedErrMessage string
expectedType backend.ErrorSource
}{
{
name: "Downstream error for 500 status",
statusCode: 500,
status: "500 Internal Server Error",
body: []byte("body bytes"),
expectedErrMessage: "request failed, status: 500 Internal Server Error, body: body bytes",
expectedType: backend.ErrorSourceDownstream,
},
{
name: "Plugin error for 501 status",
statusCode: 501,
status: "501 Not Implemented",
body: []byte("body bytes"),
expectedErrMessage: "request failed, status: 501 Not Implemented, body: body bytes",
expectedType: backend.ErrorSourcePlugin,
},
{
name: "Downstream error for 502 status",
statusCode: 502,
status: "502 Gateway Error",
body: []byte("body bytes"),
expectedErrMessage: "request failed, status: 502 Gateway Error, body: body bytes",
expectedType: backend.ErrorSourceDownstream,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := CreateResponseErrorFromStatusCode(tt.statusCode, tt.status, tt.body)
assert.Error(t, err)
// Check if error is of type ErrorWithSource
errorWithSource, ok := err.(backend.ErrorWithSource)
assert.True(t, ok, "error should implement ErrorWithSource")
// Validate the source of the error
assert.Equal(t, tt.expectedType, errorWithSource.ErrorSource())
assert.Contains(t, err.Error(), tt.expectedErrMessage)
})
}
}