AzureMonitor: Fixes metric definition for Azure Storage queue/file/blob/table resources. (#49101)

* Appropriately set metric definition

- Nested storage account resources (queues/blobs/tables/files) require metric definition of Microsoft.Storage/storageAccounts
- Update tests accordingly

* Restructure getResourceNames test

- Add expect on getResource args

* Update to fix issue for new query editor

- Reconstruct resourceUri if the resource is a storage account
- Correctly push storage namespaces as options for metric namespaces
- Filter options appropriately

* Fix duplicate options

* Fix lint issues

* Add comment explaining URI modification
This commit is contained in:
Andreas Christou
2022-05-20 16:36:42 +01:00
committed by GitHub
parent 061055fac9
commit 34fa7b493c
5 changed files with 60 additions and 14 deletions
@@ -1,3 +1,5 @@
import { startsWith } from 'lodash';
import { DataSourceInstanceSettings } from '@grafana/data';
import { TemplateSrv } from 'app/features/templating/template_srv';
@@ -445,25 +447,28 @@ describe('AzureMonitorDatasource', () => {
],
};
beforeEach(() => {
it('should return list of Resource Names', () => {
metricDefinition = 'Microsoft.Storage/storageAccounts/blobServices';
const validMetricDefinition = startsWith(metricDefinition, 'Microsoft.Storage/storageAccounts/')
? 'Microsoft.Storage/storageAccounts'
: metricDefinition;
ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockImplementation((path: string) => {
const basePath = `azuremonitor/subscriptions/${subscription}/resourceGroups`;
expect(path).toBe(
basePath +
`/${resourceGroup}/resources?$filter=resourceType eq '${metricDefinition}'&api-version=2021-04-01`
`/${resourceGroup}/resources?$filter=resourceType eq '${validMetricDefinition}'&api-version=2021-04-01`
);
return Promise.resolve(response);
});
});
it('should return list of Resource Names', () => {
metricDefinition = 'Microsoft.Storage/storageAccounts/blobServices';
return ctx.ds
.getResourceNames(subscription, resourceGroup, metricDefinition)
.then((results: Array<{ text: string; value: string }>) => {
expect(results.length).toEqual(1);
expect(results[0].text).toEqual('storagetest/default');
expect(results[0].value).toEqual('storagetest/default');
expect(ctx.ds.azureMonitorDatasource.getResource).toHaveBeenCalledWith(
`azuremonitor/subscriptions/${subscription}/resourceGroups/${resourceGroup}/resources?$filter=resourceType eq '${validMetricDefinition}'&api-version=2021-04-01`
);
});
});
});
@@ -1,4 +1,4 @@
import { filter, startsWith } from 'lodash';
import { filter, find, startsWith } from 'lodash';
import { DataSourceInstanceSettings, ScopedVars } from '@grafana/data';
import { DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime';
@@ -207,9 +207,12 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
}
getResourceNames(subscriptionId: string, resourceGroup: string, metricDefinition: string, skipToken?: string) {
const validMetricDefinition = startsWith(metricDefinition, 'Microsoft.Storage/storageAccounts/')
? 'Microsoft.Storage/storageAccounts'
: metricDefinition;
let url =
`${this.resourcePath}/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/resources?` +
`$filter=resourceType eq '${metricDefinition}'&` +
`$filter=resourceType eq '${validMetricDefinition}'&` +
`api-version=${this.listByResourceGroupApiVersion}`;
if (skipToken) {
url += `&$skiptoken=${skipToken}`;
@@ -247,9 +250,27 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
this.apiPreviewVersion,
this.replaceTemplateVariables(query)
);
return this.getResource(url).then((result: AzureMonitorMetricNamespacesResponse) => {
return ResponseParser.parseResponseValues(result, 'name', 'properties.metricNamespaceName');
});
return this.getResource(url)
.then((result: AzureMonitorMetricNamespacesResponse) => {
return ResponseParser.parseResponseValues(result, 'name', 'properties.metricNamespaceName');
})
.then((result) => {
if (url.includes('Microsoft.Storage/storageAccounts')) {
const storageNamespaces = [
'Microsoft.Storage/storageAccounts',
'Microsoft.Storage/storageAccounts/blobServices',
'Microsoft.Storage/storageAccounts/fileServices',
'Microsoft.Storage/storageAccounts/tableServices',
'Microsoft.Storage/storageAccounts/queueServices',
];
for (const namespace of storageNamespaces) {
if (!find(result, ['value', namespace.toLowerCase()])) {
result.push({ value: namespace, text: namespace });
}
}
}
return result;
});
}
getMetricNames(query: GetMetricNamesQuery) {
@@ -31,9 +31,11 @@ const MetricNamespaceField: React.FC<MetricNamespaceFieldProps> = ({
);
const options = useMemo(() => [...metricNamespaces, variableOptionGroup], [metricNamespaces, variableOptionGroup]);
const optionValues = metricNamespaces.map((m) => m.value).concat(variableOptionGroup.options.map((p) => p.value));
const optionValues = metricNamespaces
.map((m) => m.value.toLowerCase())
.concat(variableOptionGroup.options.map((p) => p.value));
const value = query.azureMonitor?.metricNamespace;
if (value && !optionValues.includes(value)) {
if (value && !optionValues.includes(value.toLowerCase())) {
options.push({ label: value, value });
}
@@ -109,6 +109,23 @@ export function setMetricNamespace(query: AzureMonitorQuery, metricNamespace: st
return query;
}
let resourceUri = query.azureMonitor?.resourceUri;
// Storage Account URIs need to be handled differently due to the additional storage services (blob/queue/table/file).
// When one of these namespaces is selected it does not form a part of the URI for the storage account and so must be appended.
// The 'default' path must also be appended. Without these two paths any API call will fail.
if (resourceUri && metricNamespace?.includes('Microsoft.Storage/storageAccounts')) {
const splitUri = resourceUri.split('/');
const accountNameIndex = splitUri.findIndex((item) => item === 'storageAccounts') + 1;
const baseUri = splitUri.slice(0, accountNameIndex + 1).join('/');
if (metricNamespace === 'Microsoft.Storage/storageAccounts') {
resourceUri = baseUri;
} else {
const subNamespace = metricNamespace.split('/')[2];
resourceUri = `${baseUri}/${subNamespace}/default`;
}
}
return {
...query,
azureMonitor: {
@@ -118,6 +135,7 @@ export function setMetricNamespace(query: AzureMonitorQuery, metricNamespace: st
aggregation: undefined,
timeGrain: '',
dimensionFilters: [],
resourceUri,
},
};
}
@@ -144,7 +144,7 @@ function formatOptions(
const options = rawResults.map(toOption);
// account for custom values that might have been set in json file like ones crafted with a template variable (ex: "cloud-datasource-resource-$Environment")
if (selectedValue && !options.find((option) => option.value === selectedValue)) {
if (selectedValue && !options.find((option) => option.value === selectedValue.toLowerCase())) {
options.push({ label: selectedValue, value: selectedValue });
}