[Telemetry] server fetcher check all collectors ready before sending (#79398)

Co-authored-by: Alejandro Fernández Haro <afharo@gmail.com>
This commit is contained in:
Ahmad Bamieh 2020-10-05 16:16:07 +03:00 committed by GitHub
parent 519d4905bc
commit f960e89ef6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 130 additions and 22 deletions

View file

@ -23,19 +23,93 @@ import { coreMock } from '../../../core/server/mocks';
describe('FetcherTask', () => {
describe('sendIfDue', () => {
it('returns undefined and warns when it fails to get telemetry configs', async () => {
it('stops when it fails to get telemetry configs', async () => {
const initializerContext = coreMock.createPluginInitializerContext({});
const fetcherTask = new FetcherTask(initializerContext);
const mockError = new Error('Some message.');
fetcherTask['getCurrentConfigs'] = async () => {
throw mockError;
};
const getCurrentConfigs = jest.fn().mockRejectedValue(mockError);
const fetchTelemetry = jest.fn();
const sendTelemetry = jest.fn();
Object.assign(fetcherTask, {
getCurrentConfigs,
fetchTelemetry,
sendTelemetry,
});
const result = await fetcherTask['sendIfDue']();
expect(result).toBe(undefined);
expect(getCurrentConfigs).toBeCalledTimes(1);
expect(fetchTelemetry).toBeCalledTimes(0);
expect(sendTelemetry).toBeCalledTimes(0);
expect(fetcherTask['logger'].warn).toBeCalledTimes(1);
expect(fetcherTask['logger'].warn).toHaveBeenCalledWith(
`Error fetching telemetry configs: ${mockError}`
`Error getting telemetry configs. (${mockError})`
);
});
it('stops when all collectors are not ready', async () => {
const initializerContext = coreMock.createPluginInitializerContext({});
const fetcherTask = new FetcherTask(initializerContext);
const getCurrentConfigs = jest.fn().mockResolvedValue({});
const areAllCollectorsReady = jest.fn().mockResolvedValue(false);
const shouldSendReport = jest.fn().mockReturnValue(true);
const fetchTelemetry = jest.fn();
const sendTelemetry = jest.fn();
const updateReportFailure = jest.fn();
Object.assign(fetcherTask, {
getCurrentConfigs,
areAllCollectorsReady,
shouldSendReport,
fetchTelemetry,
updateReportFailure,
sendTelemetry,
});
await fetcherTask['sendIfDue']();
expect(fetchTelemetry).toBeCalledTimes(0);
expect(sendTelemetry).toBeCalledTimes(0);
expect(areAllCollectorsReady).toBeCalledTimes(1);
expect(updateReportFailure).toBeCalledTimes(0);
expect(fetcherTask['logger'].warn).toBeCalledTimes(1);
expect(fetcherTask['logger'].warn).toHaveBeenCalledWith(
`Error fetching usage. (Error: Not all collectors are ready.)`
);
});
it('fetches usage and send telemetry', async () => {
const initializerContext = coreMock.createPluginInitializerContext({});
const fetcherTask = new FetcherTask(initializerContext);
const mockTelemetryUrl = 'mock_telemetry_url';
const mockClusters = ['cluster_1', 'cluster_2'];
const getCurrentConfigs = jest.fn().mockResolvedValue({
telemetryUrl: mockTelemetryUrl,
});
const areAllCollectorsReady = jest.fn().mockResolvedValue(true);
const shouldSendReport = jest.fn().mockReturnValue(true);
const fetchTelemetry = jest.fn().mockResolvedValue(mockClusters);
const sendTelemetry = jest.fn();
const updateReportFailure = jest.fn();
Object.assign(fetcherTask, {
getCurrentConfigs,
areAllCollectorsReady,
shouldSendReport,
fetchTelemetry,
updateReportFailure,
sendTelemetry,
});
await fetcherTask['sendIfDue']();
expect(areAllCollectorsReady).toBeCalledTimes(1);
expect(fetchTelemetry).toBeCalledTimes(1);
expect(sendTelemetry).toBeCalledTimes(2);
expect(sendTelemetry).toHaveBeenNthCalledWith(1, mockTelemetryUrl, mockClusters[0]);
expect(sendTelemetry).toHaveBeenNthCalledWith(2, mockTelemetryUrl, mockClusters[1]);
expect(updateReportFailure).toBeCalledTimes(0);
});
});
});

View file

@ -22,7 +22,10 @@ import { Observable } from 'rxjs';
import { take } from 'rxjs/operators';
// @ts-ignore
import fetch from 'node-fetch';
import { TelemetryCollectionManagerPluginStart } from 'src/plugins/telemetry_collection_manager/server';
import {
TelemetryCollectionManagerPluginStart,
UsageStatsPayload,
} from 'src/plugins/telemetry_collection_manager/server';
import {
PluginInitializerContext,
Logger,
@ -94,6 +97,10 @@ export class FetcherTask {
}
}
private async areAllCollectorsReady() {
return (await this.telemetryCollectionManager?.areAllCollectorsReady()) ?? false;
}
private async sendIfDue() {
if (this.isSending) {
return;
@ -103,7 +110,7 @@ export class FetcherTask {
try {
telemetryConfig = await this.getCurrentConfigs();
} catch (err) {
this.logger.warn(`Error fetching telemetry configs: ${err}`);
this.logger.warn(`Error getting telemetry configs. (${err})`);
return;
}
@ -111,9 +118,22 @@ export class FetcherTask {
return;
}
let clusters: Array<UsageStatsPayload | string> = [];
this.isSending = true;
try {
const allCollectorsReady = await this.areAllCollectorsReady();
if (!allCollectorsReady) {
throw new Error('Not all collectors are ready.');
}
clusters = await this.fetchTelemetry();
} catch (err) {
this.logger.warn(`Error fetching usage. (${err})`);
this.isSending = false;
return;
}
try {
this.isSending = true;
const clusters = await this.fetchTelemetry();
const { telemetryUrl } = telemetryConfig;
for (const cluster of clusters) {
await this.sendTelemetry(telemetryUrl, cluster);
@ -123,7 +143,7 @@ export class FetcherTask {
} catch (err) {
await this.updateReportFailure(telemetryConfig);
this.logger.warn(`Error sending telemetry usage data: ${err}`);
this.logger.warn(`Error sending telemetry usage data. (${err})`);
}
this.isSending = false;
}

View file

@ -38,4 +38,5 @@ export {
ClusterDetails,
ClusterDetailsGetter,
LicenseGetter,
UsageStatsPayload,
} from './types';

View file

@ -67,6 +67,7 @@ export class TelemetryCollectionManagerPlugin
setCollection: this.setCollection.bind(this),
getOptInStats: this.getOptInStats.bind(this),
getStats: this.getStats.bind(this),
areAllCollectorsReady: this.areAllCollectorsReady.bind(this),
};
}
@ -75,6 +76,7 @@ export class TelemetryCollectionManagerPlugin
setCollection: this.setCollection.bind(this),
getOptInStats: this.getOptInStats.bind(this),
getStats: this.getStats.bind(this),
areAllCollectorsReady: this.areAllCollectorsReady.bind(this),
};
}
@ -185,6 +187,10 @@ export class TelemetryCollectionManagerPlugin
return [];
}
private areAllCollectorsReady = async () => {
return await this.usageCollection?.areAllCollectorsReady();
};
private getOptInStatsForCollection = async (
collection: Collection,
optInStatus: boolean,

View file

@ -34,6 +34,7 @@ export interface TelemetryCollectionManagerPluginSetup {
) => void;
getOptInStats: TelemetryCollectionManagerPlugin['getOptInStats'];
getStats: TelemetryCollectionManagerPlugin['getStats'];
areAllCollectorsReady: TelemetryCollectionManagerPlugin['areAllCollectorsReady'];
}
export interface TelemetryCollectionManagerPluginStart {
@ -42,6 +43,7 @@ export interface TelemetryCollectionManagerPluginStart {
) => void;
getOptInStats: TelemetryCollectionManagerPlugin['getOptInStats'];
getStats: TelemetryCollectionManagerPlugin['getStats'];
areAllCollectorsReady: TelemetryCollectionManagerPlugin['areAllCollectorsReady'];
}
export interface TelemetryOptInStats {

View file

@ -76,23 +76,27 @@ export class CollectorSet {
};
public areAllCollectorsReady = async (collectorSet: CollectorSet = this) => {
// Kept this for runtime validation in JS code.
if (!(collectorSet instanceof CollectorSet)) {
throw new Error(
`areAllCollectorsReady method given bad collectorSet parameter: ` + typeof collectorSet
);
}
const collectorTypesNotReady = (
await Promise.all(
[...collectorSet.collectors.values()].map(async (collector) => {
if (!(await collector.isReady())) {
return collector.type;
}
})
)
).filter((collectorType): collectorType is string => !!collectorType);
const allReady = collectorTypesNotReady.length === 0;
const collectors = [...collectorSet.collectors.values()];
const collectorsWithStatus = await Promise.all(
collectors.map(async (collector) => {
return {
isReady: await collector.isReady(),
collector,
};
})
);
const collectorsTypesNotReady = collectorsWithStatus
.filter((collectorWithStatus) => collectorWithStatus.isReady === false)
.map((collectorWithStatus) => collectorWithStatus.collector.type);
const allReady = collectorsTypesNotReady.length === 0;
if (!allReady && this.maximumWaitTimeForAllCollectorsInS >= 0) {
const nowTimestamp = +new Date();
@ -102,10 +106,11 @@ export class CollectorSet {
const timeLeftInMS = this.maximumWaitTimeForAllCollectorsInS * 1000 - timeWaitedInMS;
if (timeLeftInMS <= 0) {
this.logger.debug(
`All collectors are not ready (waiting for ${collectorTypesNotReady.join(',')}) ` +
`All collectors are not ready (waiting for ${collectorsTypesNotReady.join(',')}) ` +
`but we have waited the required ` +
`${this.maximumWaitTimeForAllCollectorsInS}s and will return data from all collectors that are ready.`
);
return true;
} else {
this.logger.debug(`All collectors are not ready. Waiting for ${timeLeftInMS}ms longer.`);