mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
AzureMonitor: API support for multiple resources (#61315)
This commit is contained in:
parent
6822298953
commit
9055e1993d
@ -8,7 +8,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"time"
|
||||
@ -36,9 +35,9 @@ type AzureLogAnalyticsQuery struct {
|
||||
ResultFormat string
|
||||
URL string
|
||||
JSON json.RawMessage
|
||||
Params url.Values
|
||||
Target string
|
||||
TimeRange backend.TimeRange
|
||||
Query string
|
||||
Resources []string
|
||||
}
|
||||
|
||||
func (e *AzureLogAnalyticsDatasource) ResourceRequest(rw http.ResponseWriter, req *http.Request, cli *http.Client) {
|
||||
@ -72,7 +71,9 @@ func getApiURL(queryJSONModel types.LogJSONQuery) string {
|
||||
azureLogAnalyticsTarget := queryJSONModel.AzureLogAnalytics
|
||||
var resourceOrWorkspace string
|
||||
|
||||
if azureLogAnalyticsTarget.Resource != "" {
|
||||
if len(azureLogAnalyticsTarget.Resources) > 0 {
|
||||
resourceOrWorkspace = azureLogAnalyticsTarget.Resources[0]
|
||||
} else if azureLogAnalyticsTarget.Resource != "" {
|
||||
resourceOrWorkspace = azureLogAnalyticsTarget.Resource
|
||||
} else {
|
||||
resourceOrWorkspace = azureLogAnalyticsTarget.Workspace
|
||||
@ -107,21 +108,25 @@ func (e *AzureLogAnalyticsDatasource) buildQueries(logger log.Logger, queries []
|
||||
|
||||
apiURL := getApiURL(queryJSONModel)
|
||||
|
||||
params := url.Values{}
|
||||
rawQuery, err := macros.KqlInterpolate(logger, query, dsInfo, azureLogAnalyticsTarget.Query, "TimeGenerated")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params.Add("query", rawQuery)
|
||||
|
||||
resources := []string{}
|
||||
if len(azureLogAnalyticsTarget.Resources) > 0 {
|
||||
resources = azureLogAnalyticsTarget.Resources
|
||||
} else if azureLogAnalyticsTarget.Resource != "" {
|
||||
resources = []string{azureLogAnalyticsTarget.Resource}
|
||||
}
|
||||
azureLogAnalyticsQueries = append(azureLogAnalyticsQueries, &AzureLogAnalyticsQuery{
|
||||
RefID: query.RefID,
|
||||
ResultFormat: resultFormat,
|
||||
URL: apiURL,
|
||||
JSON: query.JSON,
|
||||
Params: params,
|
||||
Target: params.Encode(),
|
||||
TimeRange: query.TimeRange,
|
||||
Query: rawQuery,
|
||||
Resources: resources,
|
||||
})
|
||||
}
|
||||
|
||||
@ -138,7 +143,7 @@ func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, logger l
|
||||
&data.Frame{
|
||||
RefID: query.RefID,
|
||||
Meta: &data.FrameMeta{
|
||||
ExecutedQueryString: query.Params.Get("query"),
|
||||
ExecutedQueryString: query.Query,
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -150,17 +155,14 @@ func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, logger l
|
||||
return dataResponseErrorWithExecuted(fmt.Errorf("credentials for Log Analytics are no longer supported. Go to the data source configuration to update Azure Monitor credentials"))
|
||||
}
|
||||
|
||||
req, err := e.createRequest(ctx, logger, url)
|
||||
req, err := e.createRequest(ctx, logger, url, query)
|
||||
if err != nil {
|
||||
dataResponse.Error = err
|
||||
return dataResponse
|
||||
}
|
||||
|
||||
req.URL.Path = path.Join(req.URL.Path, query.URL)
|
||||
req.URL.RawQuery = query.Params.Encode()
|
||||
|
||||
ctx, span := tracer.Start(ctx, "azure log analytics query")
|
||||
span.SetAttributes("target", query.Target, attribute.Key("target").String(query.Target))
|
||||
span.SetAttributes("target", query.Query, attribute.Key("target").String(query.Query))
|
||||
span.SetAttributes("from", query.TimeRange.From.UnixNano()/int64(time.Millisecond), attribute.Key("from").Int64(query.TimeRange.From.UnixNano()/int64(time.Millisecond)))
|
||||
span.SetAttributes("until", query.TimeRange.To.UnixNano()/int64(time.Millisecond), attribute.Key("until").Int64(query.TimeRange.To.UnixNano()/int64(time.Millisecond)))
|
||||
span.SetAttributes("datasource_id", dsInfo.DatasourceID, attribute.Key("datasource_id").Int64(dsInfo.DatasourceID))
|
||||
@ -186,7 +188,7 @@ func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, logger l
|
||||
return dataResponseErrorWithExecuted(err)
|
||||
}
|
||||
|
||||
frame, err := ResponseTableToFrame(t, query.RefID, query.Params.Get("query"))
|
||||
frame, err := ResponseTableToFrame(t, query.RefID, query.Query)
|
||||
if err != nil {
|
||||
return dataResponseErrorWithExecuted(err)
|
||||
}
|
||||
@ -201,7 +203,7 @@ func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, logger l
|
||||
}
|
||||
|
||||
err = setAdditionalFrameMeta(frame,
|
||||
query.Params.Get("query"),
|
||||
query.Query,
|
||||
model.Get("azureLogAnalytics").Get("resource").MustString())
|
||||
if err != nil {
|
||||
frame.AppendNotices(data.Notice{Severity: data.NoticeSeverityWarning, Text: "could not add custom metadata: " + err.Error()})
|
||||
@ -235,14 +237,26 @@ func appendErrorNotice(frame *data.Frame, err *AzureLogAnalyticsAPIError) *data.
|
||||
return frame
|
||||
}
|
||||
|
||||
func (e *AzureLogAnalyticsDatasource) createRequest(ctx context.Context, logger log.Logger, url string) (*http.Request, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
func (e *AzureLogAnalyticsDatasource) createRequest(ctx context.Context, logger log.Logger, queryURL string, query *AzureLogAnalyticsQuery) (*http.Request, error) {
|
||||
body := map[string]interface{}{
|
||||
"query": query.Query,
|
||||
}
|
||||
if len(query.Resources) > 1 {
|
||||
body["resources"] = query.Resources
|
||||
}
|
||||
jsonValue, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %w", "failed to create request", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, queryURL, bytes.NewBuffer(jsonValue))
|
||||
if err != nil {
|
||||
logger.Debug("Failed to create request", "error", err)
|
||||
return nil, fmt.Errorf("%v: %w", "failed to create request", err)
|
||||
}
|
||||
req.URL.Path = "/"
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.URL.Path = path.Join(req.URL.Path, query.URL)
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
@ -3,8 +3,8 @@ package loganalytics
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@ -40,7 +40,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
|
||||
"queryType": "Azure Log Analytics",
|
||||
"azureLogAnalytics": {
|
||||
"resource": "/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.OperationalInsights/workspaces/AppInsightsTestDataWorkspace",
|
||||
"query": "query=Perf | where $__timeFilter() | where $__contains(Computer, 'comp1','comp2') | summarize avg(CounterValue) by bin(TimeGenerated, $__interval), Computer",
|
||||
"query": "Perf | where $__timeFilter() | where $__contains(Computer, 'comp1','comp2') | summarize avg(CounterValue) by bin(TimeGenerated, $__interval), Computer",
|
||||
"resultFormat": "%s"
|
||||
}
|
||||
}`, types.TimeSeries)),
|
||||
@ -57,12 +57,12 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
|
||||
"queryType": "Azure Log Analytics",
|
||||
"azureLogAnalytics": {
|
||||
"resource": "/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.OperationalInsights/workspaces/AppInsightsTestDataWorkspace",
|
||||
"query": "query=Perf | where $__timeFilter() | where $__contains(Computer, 'comp1','comp2') | summarize avg(CounterValue) by bin(TimeGenerated, $__interval), Computer",
|
||||
"query": "Perf | where $__timeFilter() | where $__contains(Computer, 'comp1','comp2') | summarize avg(CounterValue) by bin(TimeGenerated, $__interval), Computer",
|
||||
"resultFormat": "%s"
|
||||
}
|
||||
}`, types.TimeSeries)),
|
||||
Params: url.Values{"query": {"query=Perf | where ['TimeGenerated'] >= datetime('2018-03-15T13:00:00Z') and ['TimeGenerated'] <= datetime('2018-03-15T13:34:00Z') | where ['Computer'] in ('comp1','comp2') | summarize avg(CounterValue) by bin(TimeGenerated, 34000ms), Computer"}},
|
||||
Target: "query=query%3DPerf+%7C+where+%5B%27TimeGenerated%27%5D+%3E%3D+datetime%28%272018-03-15T13%3A00%3A00Z%27%29+and+%5B%27TimeGenerated%27%5D+%3C%3D+datetime%28%272018-03-15T13%3A34%3A00Z%27%29+%7C+where+%5B%27Computer%27%5D+in+%28%27comp1%27%2C%27comp2%27%29+%7C+summarize+avg%28CounterValue%29+by+bin%28TimeGenerated%2C+34000ms%29%2C+Computer",
|
||||
Query: "Perf | where ['TimeGenerated'] >= datetime('2018-03-15T13:00:00Z') and ['TimeGenerated'] <= datetime('2018-03-15T13:34:00Z') | where ['Computer'] in ('comp1','comp2') | summarize avg(CounterValue) by bin(TimeGenerated, 34000ms), Computer",
|
||||
Resources: []string{"/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.OperationalInsights/workspaces/AppInsightsTestDataWorkspace"},
|
||||
TimeRange: timeRange,
|
||||
},
|
||||
},
|
||||
@ -77,7 +77,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
|
||||
"queryType": "Azure Log Analytics",
|
||||
"azureLogAnalytics": {
|
||||
"workspace": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
|
||||
"query": "query=Perf",
|
||||
"query": "Perf",
|
||||
"resultFormat": "%s"
|
||||
}
|
||||
}`, types.TimeSeries)),
|
||||
@ -93,12 +93,12 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
|
||||
"queryType": "Azure Log Analytics",
|
||||
"azureLogAnalytics": {
|
||||
"workspace": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
|
||||
"query": "query=Perf",
|
||||
"query": "Perf",
|
||||
"resultFormat": "%s"
|
||||
}
|
||||
}`, types.TimeSeries)),
|
||||
Params: url.Values{"query": {"query=Perf"}},
|
||||
Target: "query=query%3DPerf",
|
||||
Query: "Perf",
|
||||
Resources: []string{},
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
@ -112,7 +112,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
|
||||
"queryType": "Azure Log Analytics",
|
||||
"azureLogAnalytics": {
|
||||
"workspace": "/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.OperationalInsights/workspaces/AppInsightsTestDataWorkspace",
|
||||
"query": "query=Perf",
|
||||
"query": "Perf",
|
||||
"resultFormat": "%s"
|
||||
}
|
||||
}`, types.TimeSeries)),
|
||||
@ -128,26 +128,26 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
|
||||
"queryType": "Azure Log Analytics",
|
||||
"azureLogAnalytics": {
|
||||
"workspace": "/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.OperationalInsights/workspaces/AppInsightsTestDataWorkspace",
|
||||
"query": "query=Perf",
|
||||
"query": "Perf",
|
||||
"resultFormat": "%s"
|
||||
}
|
||||
}`, types.TimeSeries)),
|
||||
Params: url.Values{"query": {"query=Perf"}},
|
||||
Target: "query=query%3DPerf",
|
||||
Query: "Perf",
|
||||
Resources: []string{},
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
},
|
||||
|
||||
{
|
||||
name: "Queries with a Resource should use resource-centric url",
|
||||
name: "Queries with multiple resources",
|
||||
queryModel: []backend.DataQuery{
|
||||
{
|
||||
JSON: []byte(fmt.Sprintf(`{
|
||||
"queryType": "Azure Log Analytics",
|
||||
"azureLogAnalytics": {
|
||||
"resource": "/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.OperationalInsights/workspaces/AppInsightsTestDataWorkspace",
|
||||
"query": "query=Perf",
|
||||
"query": "Perf",
|
||||
"resultFormat": "%s"
|
||||
}
|
||||
}`, types.TimeSeries)),
|
||||
@ -163,12 +163,48 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
|
||||
"queryType": "Azure Log Analytics",
|
||||
"azureLogAnalytics": {
|
||||
"resource": "/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.OperationalInsights/workspaces/AppInsightsTestDataWorkspace",
|
||||
"query": "query=Perf",
|
||||
"query": "Perf",
|
||||
"resultFormat": "%s"
|
||||
}
|
||||
}`, types.TimeSeries)),
|
||||
Params: url.Values{"query": {"query=Perf"}},
|
||||
Target: "query=query%3DPerf",
|
||||
Query: "Perf",
|
||||
Resources: []string{"/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.OperationalInsights/workspaces/AppInsightsTestDataWorkspace"},
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "Query with multiple resources",
|
||||
queryModel: []backend.DataQuery{
|
||||
{
|
||||
JSON: []byte(fmt.Sprintf(`{
|
||||
"queryType": "Azure Log Analytics",
|
||||
"azureLogAnalytics": {
|
||||
"resources": ["/subscriptions/r1","/subscriptions/r2"],
|
||||
"query": "Perf",
|
||||
"resultFormat": "%s"
|
||||
}
|
||||
}`, types.TimeSeries)),
|
||||
RefID: "A",
|
||||
TimeRange: timeRange,
|
||||
},
|
||||
},
|
||||
azureLogAnalyticsQueries: []*AzureLogAnalyticsQuery{
|
||||
{
|
||||
RefID: "A",
|
||||
ResultFormat: types.TimeSeries,
|
||||
URL: "v1/subscriptions/r1/query",
|
||||
JSON: []byte(fmt.Sprintf(`{
|
||||
"queryType": "Azure Log Analytics",
|
||||
"azureLogAnalytics": {
|
||||
"resources": ["/subscriptions/r1","/subscriptions/r2"],
|
||||
"query": "Perf",
|
||||
"resultFormat": "%s"
|
||||
}
|
||||
}`, types.TimeSeries)),
|
||||
Query: "Perf",
|
||||
Resources: []string{"/subscriptions/r1", "/subscriptions/r2"},
|
||||
TimeRange: timeRange,
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
@ -188,35 +224,44 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
|
||||
|
||||
func TestLogAnalyticsCreateRequest(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
url := "http://ds"
|
||||
url := "http://ds/"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
expectedURL string
|
||||
expectedHeaders http.Header
|
||||
Err require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "creates a request",
|
||||
expectedURL: "http://ds/",
|
||||
expectedHeaders: http.Header{"Content-Type": []string{"application/json"}},
|
||||
Err: require.NoError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ds := AzureLogAnalyticsDatasource{}
|
||||
req, err := ds.createRequest(ctx, logger, url)
|
||||
tt.Err(t, err)
|
||||
if req.URL.String() != tt.expectedURL {
|
||||
t.Errorf("Expecting %s, got %s", tt.expectedURL, req.URL.String())
|
||||
}
|
||||
if !cmp.Equal(req.Header, tt.expectedHeaders) {
|
||||
t.Errorf("Unexpected HTTP headers: %v", cmp.Diff(req.Header, tt.expectedHeaders))
|
||||
}
|
||||
t.Run("creates a request", func(t *testing.T) {
|
||||
ds := AzureLogAnalyticsDatasource{}
|
||||
req, err := ds.createRequest(ctx, logger, url, &AzureLogAnalyticsQuery{
|
||||
Resources: []string{"r"},
|
||||
Query: "Perf",
|
||||
})
|
||||
}
|
||||
require.NoError(t, err)
|
||||
if req.URL.String() != url {
|
||||
t.Errorf("Expecting %s, got %s", url, req.URL.String())
|
||||
}
|
||||
expectedHeaders := http.Header{"Content-Type": []string{"application/json"}}
|
||||
if !cmp.Equal(req.Header, expectedHeaders) {
|
||||
t.Errorf("Unexpected HTTP headers: %v", cmp.Diff(req.Header, expectedHeaders))
|
||||
}
|
||||
expectedBody := `{"query":"Perf"}`
|
||||
body, err := io.ReadAll(req.Body)
|
||||
require.NoError(t, err)
|
||||
if !cmp.Equal(string(body), expectedBody) {
|
||||
t.Errorf("Unexpected Body: %v", cmp.Diff(string(body), expectedBody))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("creates a request with multiple resources", func(t *testing.T) {
|
||||
ds := AzureLogAnalyticsDatasource{}
|
||||
req, err := ds.createRequest(ctx, logger, url, &AzureLogAnalyticsQuery{
|
||||
Resources: []string{"r1", "r2"},
|
||||
Query: "Perf",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
expectedBody := `{"query":"Perf","resources":["r1","r2"]}`
|
||||
body, err := io.ReadAll(req.Body)
|
||||
require.NoError(t, err)
|
||||
if !cmp.Equal(string(body), expectedBody) {
|
||||
t.Errorf("Unexpected Body: %v", cmp.Diff(string(body), expectedBody))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_executeQueryErrorWithDifferentLogAnalyticsCreds(t *testing.T) {
|
||||
@ -231,7 +276,6 @@ func Test_executeQueryErrorWithDifferentLogAnalyticsCreds(t *testing.T) {
|
||||
}
|
||||
ctx := context.Background()
|
||||
query := &AzureLogAnalyticsQuery{
|
||||
Params: url.Values{},
|
||||
TimeRange: backend.TimeRange{},
|
||||
}
|
||||
tracer := tracing.InitializeTracerForTest()
|
||||
|
@ -180,12 +180,14 @@ func (a AzureMonitorDimensionFilter) ConstructFiltersString() string {
|
||||
// LogJSONQuery is the frontend JSON query model for an Azure Log Analytics query.
|
||||
type LogJSONQuery struct {
|
||||
AzureLogAnalytics struct {
|
||||
Query string `json:"query"`
|
||||
ResultFormat string `json:"resultFormat"`
|
||||
Resource string `json:"resource"`
|
||||
Query string `json:"query"`
|
||||
ResultFormat string `json:"resultFormat"`
|
||||
Resources []string `json:"resources"`
|
||||
|
||||
// Deprecated: Queries should be migrated to use Resource instead
|
||||
Workspace string `json:"workspace"`
|
||||
// Deprecated: Use Resources instead
|
||||
Resource string `json:"resource"`
|
||||
} `json:"azureLogAnalytics"`
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user