diff --git a/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource.go b/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource.go index c639ac3a3e9..3def772d76b 100644 --- a/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource.go +++ b/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource.go @@ -81,13 +81,15 @@ func (e *AzureMonitorDatasource) buildQueries(queries []backend.DataQuery, dsInf urlComponents["resourceName"] = azJSONModel.ResourceName ub := urlBuilder{ + ResourceURI: azJSONModel.ResourceURI, + // Legacy, used to reconstruct resource URI if it's not present DefaultSubscription: dsInfo.Settings.SubscriptionId, Subscription: queryJSONModel.Subscription, - ResourceGroup: queryJSONModel.AzureMonitor.ResourceGroup, + ResourceGroup: azJSONModel.ResourceGroup, MetricDefinition: azJSONModel.MetricDefinition, ResourceName: azJSONModel.ResourceName, } - azureURL := ub.Build() + azureURL := ub.BuildMetricsURL() alias := azJSONModel.Alias diff --git a/pkg/tsdb/azuremonitor/metrics/url-builder.go b/pkg/tsdb/azuremonitor/metrics/url-builder.go index 53bee4f2269..4745f3bcdf0 100644 --- a/pkg/tsdb/azuremonitor/metrics/url-builder.go +++ b/pkg/tsdb/azuremonitor/metrics/url-builder.go @@ -7,6 +7,10 @@ import ( // urlBuilder builds the URL for calling the Azure Monitor API type urlBuilder struct { + ResourceURI string + + // Following fields will be deprecated in grafana 9 and will not included in new queries. + // For backwards compat, we recreate the ResourceURI using these fields DefaultSubscription string Subscription string ResourceGroup string @@ -14,26 +18,39 @@ type urlBuilder struct { ResourceName string } -// Build checks the metric definition property to see which form of the url +// BuildMetricsURL checks the metric definition property to see which form of the url // should be returned -func (ub *urlBuilder) Build() string { - subscription := ub.Subscription +func (params *urlBuilder) BuildMetricsURL() string { + resourceURI := params.ResourceURI - if ub.Subscription == "" { - subscription = ub.DefaultSubscription + // Prior to Grafana 9, we had a legacy query object rather than a resourceURI, so we manually create the resource URI + if resourceURI == "" { + subscription := params.Subscription + + if params.Subscription == "" { + subscription = params.DefaultSubscription + } + + metricDefinitionArray := strings.Split(params.MetricDefinition, "/") + resourceNameArray := strings.Split(params.ResourceName, "/") + provider := metricDefinitionArray[0] + metricDefinitionArray = metricDefinitionArray[1:] + + urlArray := []string{ + subscription, + "resourceGroups", + params.ResourceGroup, + "providers", + provider, + } + + for i := range metricDefinitionArray { + urlArray = append(urlArray, metricDefinitionArray[i]) + urlArray = append(urlArray, resourceNameArray[i]) + } + + resourceURI = strings.Join(urlArray[:], "/") } - metricDefinitionArray := strings.Split(ub.MetricDefinition, "/") - resourceNameArray := strings.Split(ub.ResourceName, "/") - provider := metricDefinitionArray[0] - metricDefinitionArray = metricDefinitionArray[1:] - - urlArray := []string{subscription, "resourceGroups", ub.ResourceGroup, "providers", provider} - - for i := range metricDefinitionArray { - urlArray = append(urlArray, metricDefinitionArray[i]) - urlArray = append(urlArray, resourceNameArray[i]) - } - - return fmt.Sprintf("%s/providers/microsoft.insights/metrics", strings.Join(urlArray[:], "/")) + return fmt.Sprintf("%s/providers/microsoft.insights/metrics", resourceURI) } diff --git a/pkg/tsdb/azuremonitor/metrics/url-builder_test.go b/pkg/tsdb/azuremonitor/metrics/url-builder_test.go index f6bb94a4664..458cf7b491c 100644 --- a/pkg/tsdb/azuremonitor/metrics/url-builder_test.go +++ b/pkg/tsdb/azuremonitor/metrics/url-builder_test.go @@ -8,65 +8,89 @@ import ( func TestURLBuilder(t *testing.T) { t.Run("AzureMonitor URL Builder", func(t *testing.T) { - t.Run("when metric definition is in the short form", func(t *testing.T) { + t.Run("when only resource uri is provided it returns resource/uri/providers/microsoft.insights/metrics", func(t *testing.T) { ub := &urlBuilder{ - DefaultSubscription: "default-sub", - ResourceGroup: "rg", - MetricDefinition: "Microsoft.Compute/virtualMachines", - ResourceName: "rn", + ResourceURI: "resource/uri", } - url := ub.Build() - require.Equal(t, url, "default-sub/resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/rn/providers/microsoft.insights/metrics") + url := ub.BuildMetricsURL() + require.Equal(t, url, "resource/uri/providers/microsoft.insights/metrics") }) - t.Run("when metric definition is in the short form and a subscription is defined", func(t *testing.T) { - ub := &urlBuilder{ - DefaultSubscription: "default-sub", - Subscription: "specified-sub", - ResourceGroup: "rg", - MetricDefinition: "Microsoft.Compute/virtualMachines", - ResourceName: "rn", - } - - url := ub.Build() - require.Equal(t, url, "specified-sub/resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/rn/providers/microsoft.insights/metrics") - }) - - t.Run("when metric definition is Microsoft.Storage/storageAccounts/blobServices", func(t *testing.T) { - ub := &urlBuilder{ - DefaultSubscription: "default-sub", - ResourceGroup: "rg", - MetricDefinition: "Microsoft.Storage/storageAccounts/blobServices", - ResourceName: "rn1/default", - } - - url := ub.Build() - require.Equal(t, url, "default-sub/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/blobServices/default/providers/microsoft.insights/metrics") - }) - - t.Run("when metric definition is Microsoft.Storage/storageAccounts/fileServices", func(t *testing.T) { - ub := &urlBuilder{ - DefaultSubscription: "default-sub", - ResourceGroup: "rg", - MetricDefinition: "Microsoft.Storage/storageAccounts/fileServices", - ResourceName: "rn1/default", - } - - url := ub.Build() - require.Equal(t, url, "default-sub/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/fileServices/default/providers/microsoft.insights/metrics") - }) - - t.Run("when metric definition is Microsoft.NetApp/netAppAccounts/capacityPools/volumes", func(t *testing.T) { + t.Run("when resource uri and legacy fields are provided the legacy fields are ignored", func(t *testing.T) { ub := &urlBuilder{ + ResourceURI: "resource/uri", DefaultSubscription: "default-sub", ResourceGroup: "rg", MetricDefinition: "Microsoft.NetApp/netAppAccounts/capacityPools/volumes", ResourceName: "rn1/rn2/rn3", } - url := ub.Build() - require.Equal(t, url, "default-sub/resourceGroups/rg/providers/Microsoft.NetApp/netAppAccounts/rn1/capacityPools/rn2/volumes/rn3/providers/microsoft.insights/metrics") + url := ub.BuildMetricsURL() + require.Equal(t, url, "resource/uri/providers/microsoft.insights/metrics") + }) + + t.Run("Legacy URL Builder params", func(t *testing.T) { + t.Run("when metric definition is in the short form", func(t *testing.T) { + ub := &urlBuilder{ + DefaultSubscription: "default-sub", + ResourceGroup: "rg", + MetricDefinition: "Microsoft.Compute/virtualMachines", + ResourceName: "rn", + } + + url := ub.BuildMetricsURL() + require.Equal(t, url, "default-sub/resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/rn/providers/microsoft.insights/metrics") + }) + + t.Run("when metric definition is in the short form and a subscription is defined", func(t *testing.T) { + ub := &urlBuilder{ + DefaultSubscription: "default-sub", + Subscription: "specified-sub", + ResourceGroup: "rg", + MetricDefinition: "Microsoft.Compute/virtualMachines", + ResourceName: "rn", + } + + url := ub.BuildMetricsURL() + require.Equal(t, url, "specified-sub/resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/rn/providers/microsoft.insights/metrics") + }) + + t.Run("when metric definition is Microsoft.Storage/storageAccounts/blobServices", func(t *testing.T) { + ub := &urlBuilder{ + DefaultSubscription: "default-sub", + ResourceGroup: "rg", + MetricDefinition: "Microsoft.Storage/storageAccounts/blobServices", + ResourceName: "rn1/default", + } + + url := ub.BuildMetricsURL() + require.Equal(t, url, "default-sub/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/blobServices/default/providers/microsoft.insights/metrics") + }) + + t.Run("when metric definition is Microsoft.Storage/storageAccounts/fileServices", func(t *testing.T) { + ub := &urlBuilder{ + DefaultSubscription: "default-sub", + ResourceGroup: "rg", + MetricDefinition: "Microsoft.Storage/storageAccounts/fileServices", + ResourceName: "rn1/default", + } + + url := ub.BuildMetricsURL() + require.Equal(t, url, "default-sub/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/fileServices/default/providers/microsoft.insights/metrics") + }) + + t.Run("when metric definition is Microsoft.NetApp/netAppAccounts/capacityPools/volumes", func(t *testing.T) { + ub := &urlBuilder{ + DefaultSubscription: "default-sub", + ResourceGroup: "rg", + MetricDefinition: "Microsoft.NetApp/netAppAccounts/capacityPools/volumes", + ResourceName: "rn1/rn2/rn3", + } + + url := ub.BuildMetricsURL() + require.Equal(t, url, "default-sub/resourceGroups/rg/providers/Microsoft.NetApp/netAppAccounts/rn1/capacityPools/rn2/volumes/rn3/providers/microsoft.insights/metrics") + }) }) }) } diff --git a/pkg/tsdb/azuremonitor/types/types.go b/pkg/tsdb/azuremonitor/types/types.go index 6d310f850fd..4321516f375 100644 --- a/pkg/tsdb/azuremonitor/types/types.go +++ b/pkg/tsdb/azuremonitor/types/types.go @@ -109,21 +109,26 @@ type AzureResponseTable struct { // AzureMonitorJSONQuery is the frontend JSON query model for an Azure Monitor query. type AzureMonitorJSONQuery struct { AzureMonitor struct { - Aggregation string `json:"aggregation"` - Alias string `json:"alias"` + ResourceURI string `json:"resourceUri"` + MetricNamespace string `json:"metricNamespace"` + MetricName string `json:"metricName"` + + Aggregation string `json:"aggregation"` + Alias string `json:"alias"` + DimensionFilters []AzureMonitorDimensionFilter `json:"dimensionFilters"` // new model + TimeGrain string `json:"timeGrain"` + Top string `json:"top"` + + // Legecy "resource" fields from before the resource picker provided just a single ResourceURI + // These are used for pre-resource picker queries to reconstruct a resource URI + MetricDefinition string `json:"metricDefinition"` + ResourceGroup string `json:"resourceGroup"` + ResourceName string `json:"resourceName"` + AllowedTimeGrainsMs []int64 `json:"allowedTimeGrainsMs"` Dimension string `json:"dimension"` // old model DimensionFilter string `json:"dimensionFilter"` // old model Format string `json:"format"` - MetricDefinition string `json:"metricDefinition"` - MetricName string `json:"metricName"` - MetricNamespace string `json:"metricNamespace"` - ResourceGroup string `json:"resourceGroup"` - ResourceName string `json:"resourceName"` - TimeGrain string `json:"timeGrain"` - Top string `json:"top"` - - DimensionFilters []AzureMonitorDimensionFilter `json:"dimensionFilters"` // new model } `json:"azureMonitor"` Subscription string `json:"subscription"` }