Skip to content

Commit

Permalink
[Telemetry] Switch to v3 endpoint (#113525)
Browse files Browse the repository at this point in the history
  • Loading branch information
Bamieh authored Oct 20, 2021
1 parent 31fa0cb commit 00cc5fe
Show file tree
Hide file tree
Showing 23 changed files with 420 additions and 174 deletions.
33 changes: 22 additions & 11 deletions src/plugins/telemetry/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,32 @@ export const PATH_TO_ADVANCED_SETTINGS = '/app/management/kibana/settings';
*/
export const PRIVACY_STATEMENT_URL = `https://www.elastic.co/legal/privacy-statement`;

/**
* The telemetry payload content encryption encoding
*/
export const PAYLOAD_CONTENT_ENCODING = 'aes256gcm';

/**
* The endpoint version when hitting the remote telemetry service
*/
export const ENDPOINT_VERSION = 'v2';
export const ENDPOINT_VERSION = 'v3';

/**
* The staging telemetry endpoint for the remote telemetry service.
*/

export const ENDPOINT_STAGING = 'https://telemetry-staging.elastic.co/';

/**
* The production telemetry endpoint for the remote telemetry service.
*/

export const ENDPOINT_PROD = 'https://telemetry.elastic.co/';

/**
* The telemetry endpoints for the remote telemetry service.
* The telemetry channels for the remote telemetry service.
*/
export const TELEMETRY_ENDPOINT = {
MAIN_CHANNEL: {
PROD: `https://telemetry.elastic.co/xpack/${ENDPOINT_VERSION}/send`,
STAGING: `https://telemetry-staging.elastic.co/xpack/${ENDPOINT_VERSION}/send`,
},
OPT_IN_STATUS_CHANNEL: {
PROD: `https://telemetry.elastic.co/opt_in_status/${ENDPOINT_VERSION}/send`,
STAGING: `https://telemetry-staging.elastic.co/opt_in_status/${ENDPOINT_VERSION}/send`,
},
export const TELEMETRY_CHANNELS = {
SNAPSHOT_CHANNEL: 'kibana-snapshot',
OPT_IN_STATUS_CHANNEL: 'kibana-opt_in_status',
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,55 @@
* Side Public License, v 1.
*/

import { getTelemetryChannelEndpoint } from './get_telemetry_channel_endpoint';
import { TELEMETRY_ENDPOINT } from '../constants';
import {
getTelemetryChannelEndpoint,
getChannel,
getBaseUrl,
} from './get_telemetry_channel_endpoint';

describe('getBaseUrl', () => {
it('throws on unknown env', () => {
expect(() =>
// @ts-expect-error
getBaseUrl('ANY')
).toThrowErrorMatchingInlineSnapshot(`"Unknown telemetry endpoint env ANY."`);
});

it('returns correct prod base url', () => {
const baseUrl = getBaseUrl('prod');
expect(baseUrl).toMatchInlineSnapshot(`"https://telemetry.elastic.co/"`);
});

it('returns correct staging base url', () => {
const baseUrl = getBaseUrl('staging');
expect(baseUrl).toMatchInlineSnapshot(`"https://telemetry-staging.elastic.co/"`);
});
});

describe('getChannel', () => {
it('throws on unknown channel', () => {
expect(() =>
// @ts-expect-error
getChannel('ANY')
).toThrowErrorMatchingInlineSnapshot(`"Unknown telemetry channel ANY."`);
});

it('returns correct snapshot channel name', () => {
const channelName = getChannel('snapshot');
expect(channelName).toMatchInlineSnapshot(`"kibana-snapshot"`);
});

it('returns correct optInStatus channel name', () => {
const channelName = getChannel('optInStatus');
expect(channelName).toMatchInlineSnapshot(`"kibana-opt_in_status"`);
});
});

describe('getTelemetryChannelEndpoint', () => {
it('throws on unknown env', () => {
expect(() =>
// @ts-expect-error
getTelemetryChannelEndpoint({ env: 'ANY', channelName: 'main' })
getTelemetryChannelEndpoint({ env: 'ANY', channelName: 'snapshot' })
).toThrowErrorMatchingInlineSnapshot(`"Unknown telemetry endpoint env ANY."`);
});

Expand All @@ -24,25 +65,33 @@ describe('getTelemetryChannelEndpoint', () => {
).toThrowErrorMatchingInlineSnapshot(`"Unknown telemetry channel ANY."`);
});

describe('main channel', () => {
describe('snapshot channel', () => {
it('returns correct prod endpoint', () => {
const endpoint = getTelemetryChannelEndpoint({ env: 'prod', channelName: 'main' });
expect(endpoint).toBe(TELEMETRY_ENDPOINT.MAIN_CHANNEL.PROD);
const endpoint = getTelemetryChannelEndpoint({ env: 'prod', channelName: 'snapshot' });
expect(endpoint).toMatchInlineSnapshot(
`"https://telemetry.elastic.co/v3/send/kibana-snapshot"`
);
});
it('returns correct staging endpoint', () => {
const endpoint = getTelemetryChannelEndpoint({ env: 'staging', channelName: 'main' });
expect(endpoint).toBe(TELEMETRY_ENDPOINT.MAIN_CHANNEL.STAGING);
const endpoint = getTelemetryChannelEndpoint({ env: 'staging', channelName: 'snapshot' });
expect(endpoint).toMatchInlineSnapshot(
`"https://telemetry-staging.elastic.co/v3/send/kibana-snapshot"`
);
});
});

describe('optInStatus channel', () => {
it('returns correct prod endpoint', () => {
const endpoint = getTelemetryChannelEndpoint({ env: 'prod', channelName: 'optInStatus' });
expect(endpoint).toBe(TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL.PROD);
expect(endpoint).toMatchInlineSnapshot(
`"https://telemetry.elastic.co/v3/send/kibana-opt_in_status"`
);
});
it('returns correct staging endpoint', () => {
const endpoint = getTelemetryChannelEndpoint({ env: 'staging', channelName: 'optInStatus' });
expect(endpoint).toBe(TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL.STAGING);
expect(endpoint).toMatchInlineSnapshot(
`"https://telemetry-staging.elastic.co/v3/send/kibana-opt_in_status"`
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,48 @@
* Side Public License, v 1.
*/

import { TELEMETRY_ENDPOINT } from '../constants';
import {
ENDPOINT_VERSION,
ENDPOINT_STAGING,
ENDPOINT_PROD,
TELEMETRY_CHANNELS,
} from '../constants';

export type ChannelName = 'snapshot' | 'optInStatus';
export type TelemetryEnv = 'staging' | 'prod';
export interface GetTelemetryChannelEndpointConfig {
channelName: 'main' | 'optInStatus';
env: 'staging' | 'prod';
channelName: ChannelName;
env: TelemetryEnv;
}

export function getTelemetryChannelEndpoint({
channelName,
env,
}: GetTelemetryChannelEndpointConfig): string {
if (env !== 'staging' && env !== 'prod') {
throw new Error(`Unknown telemetry endpoint env ${env}.`);
}

const endpointEnv = env === 'staging' ? 'STAGING' : 'PROD';

export function getChannel(channelName: ChannelName): string {
switch (channelName) {
case 'main':
return TELEMETRY_ENDPOINT.MAIN_CHANNEL[endpointEnv];
case 'snapshot':
return TELEMETRY_CHANNELS.SNAPSHOT_CHANNEL;
case 'optInStatus':
return TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL[endpointEnv];
return TELEMETRY_CHANNELS.OPT_IN_STATUS_CHANNEL;
default:
throw new Error(`Unknown telemetry channel ${channelName}.`);
}
}

export function getBaseUrl(env: TelemetryEnv): string {
switch (env) {
case 'prod':
return ENDPOINT_PROD;
case 'staging':
return ENDPOINT_STAGING;
default:
throw new Error(`Unknown telemetry endpoint env ${env}.`);
}
}

export function getTelemetryChannelEndpoint({
channelName,
env,
}: GetTelemetryChannelEndpointConfig): string {
const baseUrl = getBaseUrl(env);
const channelPath = getChannel(channelName);

return `${baseUrl}${ENDPOINT_VERSION}/send/${channelPath}`;
}
6 changes: 5 additions & 1 deletion src/plugins/telemetry/common/telemetry_config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ export { getTelemetryAllowChangingOptInStatus } from './get_telemetry_allow_chan
export { getTelemetryFailureDetails } from './get_telemetry_failure_details';
export type { TelemetryFailureDetails } from './get_telemetry_failure_details';
export { getTelemetryChannelEndpoint } from './get_telemetry_channel_endpoint';
export type { GetTelemetryChannelEndpointConfig } from './get_telemetry_channel_endpoint';
export type {
GetTelemetryChannelEndpointConfig,
ChannelName,
TelemetryEnv,
} from './get_telemetry_channel_endpoint';
10 changes: 10 additions & 0 deletions src/plugins/telemetry/common/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

export type EncryptedTelemetryPayload = Array<{ clusterUuid: string; stats: string }>;
export type UnencryptedTelemetryPayload = Array<{ clusterUuid: string; stats: object }>;
28 changes: 19 additions & 9 deletions src/plugins/telemetry/public/services/telemetry_sender.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,11 @@ describe('TelemetrySender', () => {
});

it('sends report if due', async () => {
const mockClusterUuid = 'mk_uuid';
const mockTelemetryUrl = 'telemetry_cluster_url';
const mockTelemetryPayload = ['hashed_cluster_usage_data1'];
const mockTelemetryPayload = [
{ clusterUuid: mockClusterUuid, stats: 'hashed_cluster_usage_data1' },
];

const telemetryService = mockTelemetryService();
const telemetrySender = new TelemetrySender(telemetryService);
Expand All @@ -184,14 +187,21 @@ describe('TelemetrySender', () => {

expect(telemetryService.fetchTelemetry).toBeCalledTimes(1);
expect(mockFetch).toBeCalledTimes(1);
expect(mockFetch).toBeCalledWith(mockTelemetryUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Elastic-Stack-Version': telemetryService.currentKibanaVersion,
},
body: mockTelemetryPayload[0],
});
expect(mockFetch.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"telemetry_cluster_url",
Object {
"body": "hashed_cluster_usage_data1",
"headers": Object {
"Content-Type": "application/json",
"X-Elastic-Cluster-ID": "mk_uuid",
"X-Elastic-Content-Encoding": "aes256gcm",
"X-Elastic-Stack-Version": "mockKibanaVersion",
},
"method": "POST",
},
]
`);
});

it('sends report separately for every cluster', async () => {
Expand Down
20 changes: 14 additions & 6 deletions src/plugins/telemetry/public/services/telemetry_sender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@
* Side Public License, v 1.
*/

import { REPORT_INTERVAL_MS, LOCALSTORAGE_KEY } from '../../common/constants';
import {
REPORT_INTERVAL_MS,
LOCALSTORAGE_KEY,
PAYLOAD_CONTENT_ENCODING,
} from '../../common/constants';
import { TelemetryService } from './telemetry_service';
import { Storage } from '../../../kibana_utils/public';
import type { EncryptedTelemetryPayload } from '../../common/types';

export class TelemetrySender {
private readonly telemetryService: TelemetryService;
Expand Down Expand Up @@ -57,18 +62,21 @@ export class TelemetrySender {
this.isSending = true;
try {
const telemetryUrl = this.telemetryService.getTelemetryUrl();
const telemetryData: string | string[] = await this.telemetryService.fetchTelemetry();
const clusters: string[] = ([] as string[]).concat(telemetryData);
const telemetryPayload: EncryptedTelemetryPayload =
await this.telemetryService.fetchTelemetry();

await Promise.all(
clusters.map(
async (cluster) =>
telemetryPayload.map(
async ({ clusterUuid, stats }) =>
await fetch(telemetryUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Elastic-Stack-Version': this.telemetryService.currentKibanaVersion,
'X-Elastic-Cluster-ID': clusterUuid,
'X-Elastic-Content-Encoding': PAYLOAD_CONTENT_ENCODING,
},
body: cluster,
body: stats,
})
)
);
Expand Down
Loading

0 comments on commit 00cc5fe

Please sign in to comment.