mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
AzureMonitor: Improve resource picker efficiency (#93127)
* Parameterise region building metric namespace URL - Add parameter for region (this parameter takes precedence over if global is set) - Update tests - Support this parameter on the data source method * Refactor fetchAllNamespaces - Use Set rather than an array for greater performance - Request namespaces across WestEurope, EastUS, and JapanEast concurrently - Update test * Maintain existing behaviour
This commit is contained in:
parent
72bfa624ce
commit
6a3dbe7d41
@ -231,14 +231,15 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
|
|||||||
return (await Promise.all(promises)).flat();
|
return (await Promise.all(promises)).flat();
|
||||||
}
|
}
|
||||||
|
|
||||||
getMetricNamespaces(query: GetMetricNamespacesQuery, globalRegion: boolean) {
|
getMetricNamespaces(query: GetMetricNamespacesQuery, globalRegion: boolean, region?: string) {
|
||||||
const url = UrlBuilder.buildAzureMonitorGetMetricNamespacesUrl(
|
const url = UrlBuilder.buildAzureMonitorGetMetricNamespacesUrl(
|
||||||
this.resourcePath,
|
this.resourcePath,
|
||||||
this.apiPreviewVersion,
|
this.apiPreviewVersion,
|
||||||
// Only use the first query, as the metric namespaces should be the same for all queries
|
// Only use the first query, as the metric namespaces should be the same for all queries
|
||||||
this.replaceSingleTemplateVariables(query),
|
this.replaceSingleTemplateVariables(query),
|
||||||
globalRegion,
|
globalRegion,
|
||||||
this.templateSrv
|
this.templateSrv,
|
||||||
|
region
|
||||||
);
|
);
|
||||||
return this.getResource(url)
|
return this.getResource(url)
|
||||||
.then((result: AzureAPIResponse<Namespace>) => {
|
.then((result: AzureAPIResponse<Namespace>) => {
|
||||||
|
@ -142,6 +142,38 @@ describe('AzureMonitorUrlBuilder', () => {
|
|||||||
'/subscriptions/sub/resource-uri/resource/providers/microsoft.insights/metricNamespaces?api-version=2017-05-01-preview®ion=global'
|
'/subscriptions/sub/resource-uri/resource/providers/microsoft.insights/metricNamespaces?api-version=2017-05-01-preview®ion=global'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('builds a getMetricNamesnamespace url with a specific region', () => {
|
||||||
|
const url = UrlBuilder.buildAzureMonitorGetMetricNamespacesUrl(
|
||||||
|
'',
|
||||||
|
'2017-05-01-preview',
|
||||||
|
{
|
||||||
|
resourceUri: '/subscriptions/sub/resource-uri/resource',
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
templateSrv,
|
||||||
|
'testregion'
|
||||||
|
);
|
||||||
|
expect(url).toBe(
|
||||||
|
'/subscriptions/sub/resource-uri/resource/providers/microsoft.insights/metricNamespaces?api-version=2017-05-01-preview®ion=testregion'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('builds a getMetricNamesnamespace url with a specific region (overriding global)', () => {
|
||||||
|
const url = UrlBuilder.buildAzureMonitorGetMetricNamespacesUrl(
|
||||||
|
'',
|
||||||
|
'2017-05-01-preview',
|
||||||
|
{
|
||||||
|
resourceUri: '/subscriptions/sub/resource-uri/resource',
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
templateSrv,
|
||||||
|
'testregion'
|
||||||
|
);
|
||||||
|
expect(url).toBe(
|
||||||
|
'/subscriptions/sub/resource-uri/resource/providers/microsoft.insights/metricNamespaces?api-version=2017-05-01-preview®ion=testregion'
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when a resource uri and metric namespace is provided', () => {
|
describe('when a resource uri and metric namespace is provided', () => {
|
||||||
|
@ -51,7 +51,8 @@ export default class UrlBuilder {
|
|||||||
apiVersion: string,
|
apiVersion: string,
|
||||||
query: GetMetricNamespacesQuery,
|
query: GetMetricNamespacesQuery,
|
||||||
globalRegion: boolean,
|
globalRegion: boolean,
|
||||||
templateSrv: TemplateSrv
|
templateSrv: TemplateSrv,
|
||||||
|
region?: string
|
||||||
) {
|
) {
|
||||||
let resourceUri: string;
|
let resourceUri: string;
|
||||||
|
|
||||||
@ -68,7 +69,7 @@ export default class UrlBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return `${baseUrl}${resourceUri}/providers/microsoft.insights/metricNamespaces?api-version=${apiVersion}${
|
return `${baseUrl}${resourceUri}/providers/microsoft.insights/metricNamespaces?api-version=${apiVersion}${
|
||||||
globalRegion ? '®ion=global' : ''
|
region ? `®ion=${region}` : globalRegion ? '®ion=global' : ''
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -312,9 +312,33 @@ describe('AzureMonitor resourcePickerData', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
const { resourcePickerData, postResource } = createResourcePickerData([mockSubscriptionsResponse, mockResponse]);
|
const { resourcePickerData, postResource, mockDatasource } = createResourcePickerData([
|
||||||
|
mockSubscriptionsResponse,
|
||||||
|
mockResponse,
|
||||||
|
]);
|
||||||
const formattedResults = await resourcePickerData.search('vmname', 'metrics');
|
const formattedResults = await resourcePickerData.search('vmname', 'metrics');
|
||||||
expect(postResource).toBeCalledTimes(2);
|
expect(postResource).toHaveBeenCalledTimes(2);
|
||||||
|
expect(mockDatasource.azureMonitorDatasource.getMetricNamespaces).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
resourceUri: '/subscriptions/1',
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
'westeurope'
|
||||||
|
);
|
||||||
|
expect(mockDatasource.azureMonitorDatasource.getMetricNamespaces).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
resourceUri: '/subscriptions/1',
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
'eastus'
|
||||||
|
);
|
||||||
|
expect(mockDatasource.azureMonitorDatasource.getMetricNamespaces).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
resourceUri: '/subscriptions/1',
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
'japaneast'
|
||||||
|
);
|
||||||
const secondCall = postResource.mock.calls[1];
|
const secondCall = postResource.mock.calls[1];
|
||||||
const [_, postBody] = secondCall;
|
const [_, postBody] = secondCall;
|
||||||
expect(postBody.query).not.toContain('union resourcecontainers');
|
expect(postBody.query).not.toContain('union resourcecontainers');
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import { uniq } from 'lodash';
|
|
||||||
|
|
||||||
import { DataSourceInstanceSettings } from '@grafana/data';
|
import { DataSourceInstanceSettings } from '@grafana/data';
|
||||||
import { DataSourceWithBackend, reportInteraction } from '@grafana/runtime';
|
import { DataSourceWithBackend, reportInteraction } from '@grafana/runtime';
|
||||||
|
|
||||||
@ -359,28 +357,40 @@ export default class ResourcePickerData extends DataSourceWithBackend<AzureMonit
|
|||||||
private async fetchAllNamespaces() {
|
private async fetchAllNamespaces() {
|
||||||
const subscriptions = await this.getSubscriptions();
|
const subscriptions = await this.getSubscriptions();
|
||||||
reportInteraction('grafana_ds_azuremonitor_subscriptions_loaded', { subscriptions: subscriptions.length });
|
reportInteraction('grafana_ds_azuremonitor_subscriptions_loaded', { subscriptions: subscriptions.length });
|
||||||
let supportedMetricNamespaces: string[] = [];
|
|
||||||
for await (const subscription of subscriptions) {
|
let supportedMetricNamespaces: Set<string> = new Set();
|
||||||
|
// We make use of these three regions as they *should* contain every possible namespace
|
||||||
|
const regions = ['westeurope', 'eastus', 'japaneast'];
|
||||||
|
const getNamespacesForRegion = async (region: string) => {
|
||||||
const namespaces = await this.azureMonitorDatasource.getMetricNamespaces(
|
const namespaces = await this.azureMonitorDatasource.getMetricNamespaces(
|
||||||
{
|
{
|
||||||
resourceUri: `/subscriptions/${subscription.id}`,
|
// We only need to run this request against the first available subscription
|
||||||
|
resourceUri: `/subscriptions/${subscriptions[0].id}`,
|
||||||
},
|
},
|
||||||
true
|
false,
|
||||||
|
region
|
||||||
);
|
);
|
||||||
if (namespaces) {
|
if (namespaces) {
|
||||||
const namespaceVals = namespaces.map((namespace) => `"${namespace.value.toLocaleLowerCase()}"`);
|
for (const namespace of namespaces) {
|
||||||
supportedMetricNamespaces = supportedMetricNamespaces.concat(namespaceVals);
|
supportedMetricNamespaces.add(`"${namespace.value.toLocaleLowerCase()}"`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (supportedMetricNamespaces.length === 0) {
|
const promises = regions.map((region) => getNamespacesForRegion(region));
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
if (supportedMetricNamespaces.size === 0) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Unable to resolve a list of valid metric namespaces. Validate the datasource configuration is correct and required permissions have been granted for all subscriptions. Grafana requires at least the Reader role to be assigned.'
|
'Unable to resolve a list of valid metric namespaces. Validate the datasource configuration is correct and required permissions have been granted for all subscriptions. Grafana requires at least the Reader role to be assigned.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.supportedMetricNamespaces = uniq(
|
|
||||||
supportedMetricNamespaces.concat(resourceTypes.map((namespace) => `"${namespace}"`))
|
resourceTypes.forEach((namespace) => {
|
||||||
).join(',');
|
supportedMetricNamespaces.add(`"${namespace}"`);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.supportedMetricNamespaces = Array.from(supportedMetricNamespaces).join(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
parseRows(resources: Array<string | AzureMonitorResource>): ResourceRow[] {
|
parseRows(resources: Array<string | AzureMonitorResource>): ResourceRow[] {
|
||||||
|
Loading…
Reference in New Issue
Block a user