[Telemetry] Use header-based versioned APIs instead of path-based (#159839)

This commit is contained in:
Alejandro Fernández Haro 2023-08-12 23:20:06 +02:00 committed by GitHub
parent b336a195e0
commit 0284cc158d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 779 additions and 452 deletions

View file

@ -54,12 +54,12 @@ Once you set up the APM infrastructure, you can enable the APM agent and put {ki
*Prerequisites* {kib} logs are configured to be in {ecs-ref}/ecs-reference.html[ECS JSON] format to include tracing identifiers.
Open {kib} Logs and search for an operation you are interested in.
For example, suppose you want to investigate the response times for queries to the `/api/telemetry/v2/clusters/_stats` {kib} endpoint.
For example, suppose you want to investigate the response times for queries to the `/internal/telemetry/clusters/_stats` {kib} endpoint.
Open Kibana Logs and search for the HTTP server response for the endpoint. It looks similar to the following (some fields are omitted for brevity).
[source,json]
----
{
"message":"POST /api/telemetry/v2/clusters/_stats 200 1014ms - 43.2KB",
"message":"POST /internal/telemetry/clusters/_stats 200 1014ms - 43.2KB",
"log":{"level":"DEBUG","logger":"http.server.response"},
"trace":{"id":"9b99131a6f66587971ef085ef97dfd07"},
"transaction":{"id":"d0c5bbf14f5febca"}

View file

@ -6,6 +6,10 @@
* Side Public License, v 1.
*/
import {
ELASTIC_HTTP_VERSION_HEADER,
X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
} from '@kbn/core-http-common';
import { setupIntegrationEnvironment, TestEnvironmentUtils } from '../../test_utils';
describe('Files usage telemetry', () => {
@ -45,7 +49,9 @@ describe('Files usage telemetry', () => {
]);
const { body } = await request
.post(root, '/api/telemetry/v2/clusters/_stats')
.post(root, '/internal/telemetry/clusters/_stats')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({ unencrypted: true });
expect(body[0].stats.stack_stats.kibana.plugins.files).toMatchInlineSnapshot(`

View file

@ -32,6 +32,7 @@
"@kbn/core-elasticsearch-server-mocks",
"@kbn/core-saved-objects-server-mocks",
"@kbn/logging",
"@kbn/core-http-common",
],
"exclude": [
"target/**/*",

View file

@ -6,7 +6,49 @@
* Side Public License, v 1.
*/
const BASE_INTERNAL_PATH = '/internal/telemetry';
export const INTERNAL_VERSION = { version: '2' };
/**
* Fetch Telemetry Config
* @public Kept public and path-based because we know other Elastic products fetch the opt-in status via this endpoint.
*/
export const FetchTelemetryConfigRoute = '/api/telemetry/v2/config';
export const FetchTelemetryConfigRoutePathBasedV2 = '/api/telemetry/v2/config';
/**
* Fetch Telemetry Config
* @internal
*/
export const FetchTelemetryConfigRoute = `${BASE_INTERNAL_PATH}/config`;
/**
* GET/PUT Last reported date for Snapshot telemetry
* @internal
*/
export const LastReportedRoute = `${BASE_INTERNAL_PATH}/last_reported`;
/**
* Set user has seen notice
* @internal
*/
export const UserHasSeenNoticeRoute = `${BASE_INTERNAL_PATH}/userHasSeenNotice`;
/**
* Set opt-in/out status
* @internal
*/
export const OptInRoute = `${BASE_INTERNAL_PATH}/optIn`;
/**
* Fetch the Snapshot telemetry report
* @internal
*/
export const FetchSnapshotTelemetry = `${BASE_INTERNAL_PATH}/clusters/_stats`;
/**
* Get Opt-in stats
* @internal
* @deprecated
*/
export const GetOptInStatsRoutePathBasedV2 = '/api/telemetry/v2/clusters/_opt_in_stats';

View file

@ -8,4 +8,5 @@
export * from './latest';
export * as v1 from './v2'; // Just so v1 can also be used (but for some reason telemetry endpoints have always been v2 :shrug:)
export * as v2 from './v2';

View file

@ -24,7 +24,7 @@ import type { HomePublicPluginSetup } from '@kbn/home-plugin/public';
import { ElasticV3BrowserShipper } from '@kbn/analytics-shippers-elastic-v3-browser';
import { of } from 'rxjs';
import { FetchTelemetryConfigRoute } from '../common/routes';
import { FetchTelemetryConfigRoute, INTERNAL_VERSION } from '../common/routes';
import type { v2 } from '../common/types';
import { TelemetrySender, TelemetryService, TelemetryNotifications } from './services';
import { renderWelcomeTelemetryNotice } from './render_welcome_telemetry_notice';
@ -329,7 +329,7 @@ export class TelemetryPlugin
*/
private async fetchUpdatedConfig(http: HttpStart | HttpSetup): Promise<TelemetryPluginConfig> {
const { allowChangingOptInStatus, optIn, sendUsageFrom, telemetryNotifyUserAboutOptInDefault } =
await http.get<v2.FetchTelemetryConfigResponse>(FetchTelemetryConfigRoute);
await http.get<v2.FetchTelemetryConfigResponse>(FetchTelemetryConfigRoute, INTERNAL_VERSION);
return {
...this.config,

View file

@ -10,6 +10,12 @@
/* eslint-disable dot-notation */
import { mockTelemetryService } from '../mocks';
import {
FetchSnapshotTelemetry,
INTERNAL_VERSION,
OptInRoute,
UserHasSeenNoticeRoute,
} from '../../common/routes';
describe('TelemetryService', () => {
describe('fetchTelemetry', () => {
@ -17,7 +23,8 @@ describe('TelemetryService', () => {
const telemetryService = mockTelemetryService();
await telemetryService.fetchTelemetry();
expect(telemetryService['http'].post).toBeCalledWith('/api/telemetry/v2/clusters/_stats', {
expect(telemetryService['http'].post).toBeCalledWith(FetchSnapshotTelemetry, {
...INTERNAL_VERSION,
body: JSON.stringify({ unencrypted: false, refreshCache: false }),
});
});
@ -64,7 +71,8 @@ describe('TelemetryService', () => {
const optedIn = true;
await telemetryService.setOptIn(optedIn);
expect(telemetryService['http'].post).toBeCalledWith('/api/telemetry/v2/optIn', {
expect(telemetryService['http'].post).toBeCalledWith(OptInRoute, {
...INTERNAL_VERSION,
body: JSON.stringify({ enabled: optedIn }),
});
});
@ -77,7 +85,8 @@ describe('TelemetryService', () => {
const optedIn = false;
await telemetryService.setOptIn(optedIn);
expect(telemetryService['http'].post).toBeCalledWith('/api/telemetry/v2/optIn', {
expect(telemetryService['http'].post).toBeCalledWith(OptInRoute, {
...INTERNAL_VERSION,
body: JSON.stringify({ enabled: optedIn }),
});
});
@ -110,7 +119,7 @@ describe('TelemetryService', () => {
config: { allowChangingOptInStatus: true },
});
telemetryService['http'].post = jest.fn().mockImplementation((url: string) => {
if (url === '/api/telemetry/v2/optIn') {
if (url === OptInRoute) {
throw Error('failed to update opt in.');
}
});
@ -203,7 +212,7 @@ describe('TelemetryService', () => {
});
telemetryService['http'].put = jest.fn().mockImplementation((url: string) => {
if (url === '/api/telemetry/v2/userHasSeenNotice') {
if (url === UserHasSeenNoticeRoute) {
throw Error('failed to update opt in.');
}
});

View file

@ -8,11 +8,19 @@
import { i18n } from '@kbn/i18n';
import type { CoreSetup, CoreStart } from '@kbn/core/public';
import {
LastReportedRoute,
INTERNAL_VERSION,
OptInRoute,
FetchSnapshotTelemetry,
UserHasSeenNoticeRoute,
} from '../../common/routes';
import type { TelemetryPluginConfig } from '../plugin';
import { getTelemetryChannelEndpoint } from '../../common/telemetry_config/get_telemetry_channel_endpoint';
import { getTelemetryChannelEndpoint } from '../../common/telemetry_config';
import type {
UnencryptedTelemetryPayload,
EncryptedTelemetryPayload,
FetchLastReportedResponse,
} from '../../common/types/latest';
import { PAYLOAD_CONTENT_ENCODING } from '../../common/constants';
@ -93,8 +101,7 @@ export class TelemetryService {
/** Is the cluster allowed to change the opt-in/out status **/
public getCanChangeOptInStatus = () => {
const allowChangingOptInStatus = this.config.allowChangingOptInStatus;
return allowChangingOptInStatus;
return this.config.allowChangingOptInStatus;
};
/** Retrieve the opt-in/out notification URL **/
@ -156,17 +163,18 @@ export class TelemetryService {
};
public fetchLastReported = async (): Promise<number | undefined> => {
const response = await this.http.get<{ lastReported?: number }>(
'/api/telemetry/v2/last_reported'
const response = await this.http.get<FetchLastReportedResponse>(
LastReportedRoute,
INTERNAL_VERSION
);
return response?.lastReported;
};
public updateLastReported = async (): Promise<number | undefined> => {
return this.http.put('/api/telemetry/v2/last_reported');
return this.http.put(LastReportedRoute);
};
/** Fetches an unencrypted telemetry payload so we can show it to the user **/
/** Fetches an unencrypted telemetry payload, so we can show it to the user **/
public fetchExample = async (): Promise<UnencryptedTelemetryPayload> => {
return await this.fetchTelemetry({ unencrypted: true, refreshCache: true });
};
@ -174,12 +182,14 @@ export class TelemetryService {
/**
* Fetches telemetry payload
* @param unencrypted Default `false`. Whether the returned payload should be encrypted or not.
* @param refreshCache Default `false`. Set to `true` to force the regeneration of the telemetry report.
*/
public fetchTelemetry = async <T = EncryptedTelemetryPayload | UnencryptedTelemetryPayload>({
unencrypted = false,
refreshCache = false,
} = {}): Promise<T> => {
return this.http.post('/api/telemetry/v2/clusters/_stats', {
return this.http.post(FetchSnapshotTelemetry, {
...INTERNAL_VERSION,
body: JSON.stringify({ unencrypted, refreshCache }),
});
};
@ -198,12 +208,10 @@ export class TelemetryService {
try {
// Report the option to the Kibana server to store the settings.
// It returns the encrypted update to send to the telemetry cluster [{cluster_uuid, opt_in_status}]
const optInStatusPayload = await this.http.post<EncryptedTelemetryPayload>(
'/api/telemetry/v2/optIn',
{
body: JSON.stringify({ enabled: optedIn }),
}
);
const optInStatusPayload = await this.http.post<EncryptedTelemetryPayload>(OptInRoute, {
...INTERNAL_VERSION,
body: JSON.stringify({ enabled: optedIn }),
});
if (this.reportOptInStatusChange) {
// Use the response to report about the change to the remote telemetry cluster.
// If it's opt-out, this will be the last communication to the remote service.
@ -231,7 +239,7 @@ export class TelemetryService {
*/
public setUserHasSeenNotice = async (): Promise<void> => {
try {
await this.http.put('/api/telemetry/v2/userHasSeenNotice');
await this.http.put(UserHasSeenNoticeRoute, INTERNAL_VERSION);
this.userHasSeenOptedInNotice = true;
} catch (error) {
this.notifications.toasts.addError(error, {
@ -248,7 +256,7 @@ export class TelemetryService {
/**
* Pushes the encrypted payload [{cluster_uuid, opt_in_status}] to the remote telemetry service
* @param optInPayload [{cluster_uuid, opt_in_status}] encrypted by the server into an array of strings
* @param optInStatusPayload [{cluster_uuid, opt_in_status}] encrypted by the server into an array of strings
*/
private reportOptInStatus = async (
optInStatusPayload: EncryptedTelemetryPayload

View file

@ -8,9 +8,14 @@
import { type Observable, firstValueFrom } from 'rxjs';
import type { IRouter, SavedObjectsClient } from '@kbn/core/server';
import { schema } from '@kbn/config-schema';
import { RequestHandler } from '@kbn/core-http-server';
import type { TelemetryConfigType } from '../config';
import { v2 } from '../../common/types';
import { FetchTelemetryConfigRoute } from '../../common/routes';
import {
FetchTelemetryConfigRoutePathBasedV2,
FetchTelemetryConfigRoute,
} from '../../common/routes';
import { getTelemetrySavedObject } from '../saved_objects';
import {
getNotifyUserAboutOptInDefault,
@ -25,54 +30,74 @@ interface RegisterTelemetryConfigRouteOptions {
currentKibanaVersion: string;
savedObjectsInternalClient$: Observable<SavedObjectsClient>;
}
export function registerTelemetryConfigRoutes({
router,
config$,
currentKibanaVersion,
savedObjectsInternalClient$,
}: RegisterTelemetryConfigRouteOptions) {
// GET to retrieve
router.get(
{
path: FetchTelemetryConfigRoute,
validate: false,
const v2Handler: RequestHandler = async (context, req, res) => {
const config = await firstValueFrom(config$);
const savedObjectsInternalClient = await firstValueFrom(savedObjectsInternalClient$);
const telemetrySavedObject = await getTelemetrySavedObject(savedObjectsInternalClient);
const allowChangingOptInStatus = getTelemetryAllowChangingOptInStatus({
configTelemetryAllowChangingOptInStatus: config.allowChangingOptInStatus,
telemetrySavedObject,
});
const optIn = getTelemetryOptIn({
configTelemetryOptIn: config.optIn,
allowChangingOptInStatus,
telemetrySavedObject,
currentKibanaVersion,
});
const sendUsageFrom = getTelemetrySendUsageFrom({
configTelemetrySendUsageFrom: config.sendUsageFrom,
telemetrySavedObject,
});
const telemetryNotifyUserAboutOptInDefault = getNotifyUserAboutOptInDefault({
telemetrySavedObject,
allowChangingOptInStatus,
configTelemetryOptIn: config.optIn,
telemetryOptedIn: optIn,
});
const body: v2.FetchTelemetryConfigResponse = {
allowChangingOptInStatus,
optIn,
sendUsageFrom,
telemetryNotifyUserAboutOptInDefault,
};
return res.ok({ body });
};
const v2Validations = {
response: {
200: {
body: schema.object({
allowChangingOptInStatus: schema.boolean(),
optIn: schema.oneOf([schema.boolean(), schema.literal(null)]),
sendUsageFrom: schema.oneOf([schema.literal('server'), schema.literal('browser')]),
telemetryNotifyUserAboutOptInDefault: schema.boolean(),
}),
},
},
async (context, req, res) => {
const config = await firstValueFrom(config$);
const savedObjectsInternalClient = await firstValueFrom(savedObjectsInternalClient$);
const telemetrySavedObject = await getTelemetrySavedObject(savedObjectsInternalClient);
const allowChangingOptInStatus = getTelemetryAllowChangingOptInStatus({
configTelemetryAllowChangingOptInStatus: config.allowChangingOptInStatus,
telemetrySavedObject,
});
};
const optIn = getTelemetryOptIn({
configTelemetryOptIn: config.optIn,
allowChangingOptInStatus,
telemetrySavedObject,
currentKibanaVersion,
});
// Register the internal versioned API
router.versioned
.get({ access: 'internal', path: FetchTelemetryConfigRoute })
// Just because it used to be /v2/, we are creating identical v1 and v2.
.addVersion({ version: '1', validate: v2Validations }, v2Handler)
.addVersion({ version: '2', validate: v2Validations }, v2Handler);
const sendUsageFrom = getTelemetrySendUsageFrom({
configTelemetrySendUsageFrom: config.sendUsageFrom,
telemetrySavedObject,
});
const telemetryNotifyUserAboutOptInDefault = getNotifyUserAboutOptInDefault({
telemetrySavedObject,
allowChangingOptInStatus,
configTelemetryOptIn: config.optIn,
telemetryOptedIn: optIn,
});
const body: v2.FetchTelemetryConfigResponse = {
allowChangingOptInStatus,
optIn,
sendUsageFrom,
telemetryNotifyUserAboutOptInDefault,
};
return res.ok({ body });
}
);
// Register the deprecated public and path-based for BWC
// as we know this one is used by other Elastic products to fetch the opt-in status.
router.versioned
.get({ access: 'public', path: FetchTelemetryConfigRoutePathBasedV2 })
.addVersion({ version: '2023-10-31', validate: v2Validations }, v2Handler);
}

View file

@ -6,9 +6,12 @@
* Side Public License, v 1.
*/
import { schema } from '@kbn/config-schema';
import type { IRouter, SavedObjectsClient } from '@kbn/core/server';
import type { Observable } from 'rxjs';
import { firstValueFrom } from 'rxjs';
import { RequestHandler } from '@kbn/core-http-server';
import { LastReportedRoute } from '../../common/routes';
import { v2 } from '../../common/types';
import { getTelemetrySavedObject, updateTelemetrySavedObject } from '../saved_objects';
@ -17,38 +20,38 @@ export function registerTelemetryLastReported(
savedObjectsInternalClient$: Observable<SavedObjectsClient>
) {
// GET to retrieve
router.get(
{
path: '/api/telemetry/v2/last_reported',
validate: false,
},
async (context, req, res) => {
const savedObjectsInternalClient = await firstValueFrom(savedObjectsInternalClient$);
const telemetrySavedObject = await getTelemetrySavedObject(savedObjectsInternalClient);
const v2GetValidations = {
response: { 200: { body: schema.object({ lastReported: schema.maybe(schema.number()) }) } },
};
const body: v2.FetchLastReportedResponse = {
lastReported: telemetrySavedObject && telemetrySavedObject?.lastReported,
};
const v2GetHandler: RequestHandler = async (context, req, res) => {
const savedObjectsInternalClient = await firstValueFrom(savedObjectsInternalClient$);
const telemetrySavedObject = await getTelemetrySavedObject(savedObjectsInternalClient);
return res.ok({
body,
});
}
);
const body: v2.FetchLastReportedResponse = {
lastReported: telemetrySavedObject && telemetrySavedObject?.lastReported,
};
return res.ok({ body });
};
router.versioned
.get({ access: 'internal', path: LastReportedRoute })
// Just because it used to be /v2/, we are creating identical v1 and v2.
.addVersion({ version: '1', validate: v2GetValidations }, v2GetHandler)
.addVersion({ version: '2', validate: v2GetValidations }, v2GetHandler);
// PUT to update
router.put(
{
path: '/api/telemetry/v2/last_reported',
validate: false,
},
async (context, req, res) => {
const savedObjectsInternalClient = await firstValueFrom(savedObjectsInternalClient$);
await updateTelemetrySavedObject(savedObjectsInternalClient, {
lastReported: Date.now(),
});
const v2PutHandler: RequestHandler = async (context, req, res) => {
const savedObjectsInternalClient = await firstValueFrom(savedObjectsInternalClient$);
await updateTelemetrySavedObject(savedObjectsInternalClient, {
lastReported: Date.now(),
});
return res.ok();
};
return res.ok();
}
);
router.versioned
.put({ access: 'internal', path: LastReportedRoute })
// Just because it used to be /v2/, we are creating identical v1 and v2.
.addVersion({ version: '1', validate: false }, v2PutHandler)
.addVersion({ version: '2', validate: false }, v2PutHandler);
}

View file

@ -9,12 +9,14 @@
import { firstValueFrom, type Observable } from 'rxjs';
import { schema } from '@kbn/config-schema';
import type { IRouter, Logger } from '@kbn/core/server';
import { SavedObjectsErrorHelpers } from '@kbn/core/server';
import { RequestHandlerContext, SavedObjectsErrorHelpers } from '@kbn/core/server';
import type {
StatsGetterConfig,
TelemetryCollectionManagerPluginSetup,
} from '@kbn/telemetry-collection-manager-plugin/server';
import { v2 } from '../../common/types';
import { RequestHandler } from '@kbn/core-http-server';
import { OptInRoute } from '../../common/routes';
import { OptInBody, v2 } from '../../common/types';
import { sendTelemetryOptInStatus } from './telemetry_opt_in_stats';
import {
getTelemetrySavedObject,
@ -41,78 +43,91 @@ export function registerTelemetryOptInRoutes({
currentKibanaVersion,
telemetryCollectionManager,
}: RegisterOptInRoutesParams) {
router.post(
{
path: '/api/telemetry/v2/optIn',
validate: {
body: schema.object({ enabled: schema.boolean() }),
const v2Handler: RequestHandler<undefined, undefined, OptInBody, RequestHandlerContext> = async (
context,
req,
res
) => {
const newOptInStatus = req.body.enabled;
const soClient = (await context.core).savedObjects.getClient({
includedHiddenTypes: [TELEMETRY_SAVED_OBJECT_TYPE],
});
const attributes: TelemetrySavedObject = {
enabled: newOptInStatus,
lastVersionChecked: currentKibanaVersion,
};
const config = await firstValueFrom(config$);
let telemetrySavedObject: TelemetrySavedObject | undefined;
try {
telemetrySavedObject = await getTelemetrySavedObject(soClient);
} catch (err) {
if (SavedObjectsErrorHelpers.isForbiddenError(err)) {
// If we couldn't get the saved object due to lack of permissions,
// we can assume the user won't be able to update it either
return res.forbidden();
}
}
const allowChangingOptInStatus = getTelemetryAllowChangingOptInStatus({
configTelemetryAllowChangingOptInStatus: config.allowChangingOptInStatus,
telemetrySavedObject,
});
if (!allowChangingOptInStatus) {
return res.badRequest({
body: JSON.stringify({ error: 'Not allowed to change Opt-in Status.' }),
});
}
const statsGetterConfig: StatsGetterConfig = {
unencrypted: false,
};
const optInStatus = await telemetryCollectionManager.getOptInStats(
newOptInStatus,
statsGetterConfig
);
if (config.sendUsageFrom === 'server') {
const { appendServerlessChannelsSuffix, sendUsageTo } = config;
sendTelemetryOptInStatus(
telemetryCollectionManager,
{ appendServerlessChannelsSuffix, sendUsageTo, newOptInStatus, currentKibanaVersion },
statsGetterConfig
).catch((err) => {
// The server is likely behind a firewall and can't reach the remote service
logger.warn(
`Failed to notify the telemetry endpoint about the opt-in selection. Possibly blocked by a firewall? - Error: ${err.message}`
);
});
}
try {
await updateTelemetrySavedObject(soClient, attributes);
} catch (e) {
if (SavedObjectsErrorHelpers.isForbiddenError(e)) {
return res.forbidden();
}
}
const body: v2.OptInResponse = optInStatus;
return res.ok({ body });
};
const v2Validations = {
request: { body: schema.object({ enabled: schema.boolean() }) },
response: {
200: {
body: schema.arrayOf(
schema.object({ clusterUuid: schema.string(), stats: schema.string() })
),
},
},
async (context, req, res) => {
const newOptInStatus = req.body.enabled;
const soClient = (await context.core).savedObjects.getClient({
includedHiddenTypes: [TELEMETRY_SAVED_OBJECT_TYPE],
});
const attributes: TelemetrySavedObject = {
enabled: newOptInStatus,
lastVersionChecked: currentKibanaVersion,
};
const config = await firstValueFrom(config$);
};
let telemetrySavedObject: TelemetrySavedObject | undefined;
try {
telemetrySavedObject = await getTelemetrySavedObject(soClient);
} catch (err) {
if (SavedObjectsErrorHelpers.isForbiddenError(err)) {
// If we couldn't get the saved object due to lack of permissions,
// we can assume the user won't be able to update it either
return res.forbidden();
}
}
const allowChangingOptInStatus = getTelemetryAllowChangingOptInStatus({
configTelemetryAllowChangingOptInStatus: config.allowChangingOptInStatus,
telemetrySavedObject,
});
if (!allowChangingOptInStatus) {
return res.badRequest({
body: JSON.stringify({ error: 'Not allowed to change Opt-in Status.' }),
});
}
const statsGetterConfig: StatsGetterConfig = {
unencrypted: false,
};
const optInStatus = await telemetryCollectionManager.getOptInStats(
newOptInStatus,
statsGetterConfig
);
if (config.sendUsageFrom === 'server') {
const { appendServerlessChannelsSuffix, sendUsageTo } = config;
sendTelemetryOptInStatus(
telemetryCollectionManager,
{ appendServerlessChannelsSuffix, sendUsageTo, newOptInStatus, currentKibanaVersion },
statsGetterConfig
).catch((err) => {
// The server is likely behind a firewall and can't reach the remote service
logger.warn(
`Failed to notify the telemetry endpoint about the opt-in selection. Possibly blocked by a firewall? - Error: ${err.message}`
);
});
}
try {
await updateTelemetrySavedObject(soClient, attributes);
} catch (e) {
if (SavedObjectsErrorHelpers.isForbiddenError(e)) {
return res.forbidden();
}
}
const body: v2.OptInResponse = optInStatus;
return res.ok({ body });
}
);
router.versioned
.post({ access: 'internal', path: OptInRoute })
// Just because it used to be /v2/, we are creating identical v1 and v2.
.addVersion({ version: '1', validate: v2Validations }, v2Handler)
.addVersion({ version: '2', validate: v2Validations }, v2Handler);
}

View file

@ -14,6 +14,7 @@ import type {
TelemetryCollectionManagerPluginSetup,
StatsGetterConfig,
} from '@kbn/telemetry-collection-manager-plugin/server';
import { GetOptInStatsRoutePathBasedV2 } from '../../common/routes';
import type { v2 } from '../../common/types';
import { EncryptedTelemetryPayload, UnencryptedTelemetryPayload } from '../../common/types';
import { getTelemetryChannelEndpoint } from '../../common/telemetry_config';
@ -62,43 +63,64 @@ export function registerTelemetryOptInStatsRoutes(
router: IRouter,
telemetryCollectionManager: TelemetryCollectionManagerPluginSetup
) {
router.post(
{
path: '/api/telemetry/v2/clusters/_opt_in_stats',
validate: {
body: schema.object({
enabled: schema.boolean(),
unencrypted: schema.boolean({ defaultValue: true }),
}),
router.versioned
.post({
access: 'public', // It's not used across Kibana, and I didn't want to remove it in this PR just in case.
path: GetOptInStatsRoutePathBasedV2,
})
.addVersion(
{
version: '2023-10-31',
validate: {
request: {
body: schema.object({
enabled: schema.boolean(),
unencrypted: schema.boolean({ defaultValue: true }),
}),
},
response: {
200: {
body: schema.arrayOf(
schema.object({
clusterUuid: schema.string(),
stats: schema.object({
cluster_uuid: schema.string(),
opt_in_status: schema.boolean(),
}),
})
),
},
503: { body: schema.string() },
},
},
},
},
async (context, req, res) => {
try {
const newOptInStatus = req.body.enabled;
const unencrypted = req.body.unencrypted;
async (context, req, res) => {
try {
const newOptInStatus = req.body.enabled;
const unencrypted = req.body.unencrypted;
if (!(await telemetryCollectionManager.shouldGetTelemetry())) {
// We probably won't reach here because there is a license check in the auth phase of the HTTP requests.
// But let's keep it here should that changes at any point.
return res.customError({
statusCode: 503,
body: `Can't fetch telemetry at the moment because some services are down. Check the /status page for more details.`,
});
if (!(await telemetryCollectionManager.shouldGetTelemetry())) {
// We probably won't reach here because there is a license check in the auth phase of the HTTP requests.
// But let's keep it here should that changes at any point.
return res.customError({
statusCode: 503,
body: `Can't fetch telemetry at the moment because some services are down. Check the /status page for more details.`,
});
}
const statsGetterConfig: StatsGetterConfig = {
unencrypted,
};
const optInStatus = await telemetryCollectionManager.getOptInStats(
newOptInStatus,
statsGetterConfig
);
const body: v2.OptInStatsResponse = optInStatus;
return res.ok({ body });
} catch (err) {
return res.ok({ body: [] });
}
const statsGetterConfig: StatsGetterConfig = {
unencrypted,
};
const optInStatus = await telemetryCollectionManager.getOptInStats(
newOptInStatus,
statsGetterConfig
);
const body: v2.OptInStatsResponse = optInStatus;
return res.ok({ body });
} catch (err) {
return res.ok({ body: [] });
}
}
);
);
}

View file

@ -16,8 +16,9 @@ async function runRequest(
mockRouter: IRouter<RequestHandlerContext>,
body?: { unencrypted?: boolean; refreshCache?: boolean }
) {
expect(mockRouter.post).toBeCalled();
const [, handler] = (mockRouter.post as jest.Mock).mock.calls[0];
expect(mockRouter.versioned.post).toBeCalled();
const [, handler] = (mockRouter.versioned.post as jest.Mock).mock.results[0].value.addVersion.mock
.calls[0];
const mockResponse = httpServerMock.createResponseFactory();
const mockRequest = httpServerMock.createKibanaRequest({ body });
await handler(null, mockRequest, mockResponse);
@ -49,10 +50,10 @@ describe('registerTelemetryUsageStatsRoutes', () => {
describe('clusters/_stats POST route', () => {
it('registers _stats POST route and accepts body configs', () => {
registerTelemetryUsageStatsRoutes(mockRouter, telemetryCollectionManager, true, getSecurity);
expect(mockRouter.post).toBeCalledTimes(1);
const [routeConfig, handler] = (mockRouter.post as jest.Mock).mock.calls[0];
expect(routeConfig.path).toMatchInlineSnapshot(`"/api/telemetry/v2/clusters/_stats"`);
expect(Object.keys(routeConfig.validate.body.props)).toEqual(['unencrypted', 'refreshCache']);
expect(mockRouter.versioned.post).toBeCalledTimes(1);
const [routeConfig, handler] = (mockRouter.versioned.post as jest.Mock).mock.results[0].value
.addVersion.mock.calls[0];
expect(routeConfig.version).toMatchInlineSnapshot(`"1"`);
expect(handler).toBeInstanceOf(Function);
});

View file

@ -13,7 +13,9 @@ import type {
StatsGetterConfig,
} from '@kbn/telemetry-collection-manager-plugin/server';
import type { SecurityPluginStart } from '@kbn/security-plugin/server';
import { v2 } from '../../common/types';
import { RequestHandler } from '@kbn/core-http-server';
import { FetchSnapshotTelemetry } from '../../common/routes';
import { UsageStatsBody, v2 } from '../../common/types';
export type SecurityGetter = () => SecurityPluginStart | undefined;
@ -23,64 +25,75 @@ export function registerTelemetryUsageStatsRoutes(
isDev: boolean,
getSecurity: SecurityGetter
) {
router.post(
{
path: '/api/telemetry/v2/clusters/_stats',
validate: {
body: schema.object({
unencrypted: schema.boolean({ defaultValue: false }),
refreshCache: schema.boolean({ defaultValue: false }),
}),
},
},
async (context, req, res) => {
const { unencrypted, refreshCache } = req.body;
const v2Handler: RequestHandler<undefined, undefined, UsageStatsBody> = async (
context,
req,
res
) => {
const { unencrypted, refreshCache } = req.body;
if (!(await telemetryCollectionManager.shouldGetTelemetry())) {
// We probably won't reach here because there is a license check in the auth phase of the HTTP requests.
// But let's keep it here should that changes at any point.
return res.customError({
statusCode: 503,
body: `Can't fetch telemetry at the moment because some services are down. Check the /status page for more details.`,
});
}
if (!(await telemetryCollectionManager.shouldGetTelemetry())) {
// We probably won't reach here because there is a license check in the auth phase of the HTTP requests.
// But let's keep it here should that changes at any point.
return res.customError({
statusCode: 503,
body: `Can't fetch telemetry at the moment because some services are down. Check the /status page for more details.`,
});
}
const security = getSecurity();
// We need to check useRbacForRequest to figure out if ES has security enabled before making the privileges check
if (security && unencrypted && security.authz.mode.useRbacForRequest(req)) {
// Normally we would use `options: { tags: ['access:decryptedTelemetry'] }` in the route definition to check authorization for an
// API action, however, we want to check this conditionally based on the `unencrypted` parameter. In this case we need to use the
// security API directly to check privileges for this action. Note that the 'decryptedTelemetry' API privilege string is only
// granted to users that have "Global All" or "Global Read" privileges in Kibana.
const { checkPrivilegesWithRequest, actions } = security.authz;
const privileges = { kibana: actions.api.get('decryptedTelemetry') };
const { hasAllRequested } = await checkPrivilegesWithRequest(req).globally(privileges);
if (!hasAllRequested) {
return res.forbidden();
}
}
try {
const statsConfig: StatsGetterConfig = {
unencrypted,
refreshCache: unencrypted || refreshCache,
};
const body: v2.UnencryptedTelemetryPayload = await telemetryCollectionManager.getStats(
statsConfig
);
return res.ok({ body });
} catch (err) {
if (isDev) {
// don't ignore errors when running in dev mode
throw err;
}
if (unencrypted && err.status === 403) {
return res.forbidden();
}
// ignore errors and return empty set
return res.ok({ body: [] });
const security = getSecurity();
// We need to check useRbacForRequest to figure out if ES has security enabled before making the privileges check
if (security && unencrypted && security.authz.mode.useRbacForRequest(req)) {
// Normally we would use `options: { tags: ['access:decryptedTelemetry'] }` in the route definition to check authorization for an
// API action, however, we want to check this conditionally based on the `unencrypted` parameter. In this case we need to use the
// security API directly to check privileges for this action. Note that the 'decryptedTelemetry' API privilege string is only
// granted to users that have "Global All" or "Global Read" privileges in Kibana.
const { checkPrivilegesWithRequest, actions } = security.authz;
const privileges = { kibana: actions.api.get('decryptedTelemetry') };
const { hasAllRequested } = await checkPrivilegesWithRequest(req).globally(privileges);
if (!hasAllRequested) {
return res.forbidden();
}
}
);
try {
const statsConfig: StatsGetterConfig = {
unencrypted,
refreshCache: unencrypted || refreshCache,
};
const body: v2.UnencryptedTelemetryPayload = await telemetryCollectionManager.getStats(
statsConfig
);
return res.ok({ body });
} catch (err) {
if (isDev) {
// don't ignore errors when running in dev mode
throw err;
}
if (unencrypted && err.status === 403) {
return res.forbidden();
}
// ignore errors and return empty set
return res.ok({ body: [] });
}
};
const v2Validations = {
request: {
body: schema.object({
unencrypted: schema.boolean({ defaultValue: false }),
refreshCache: schema.boolean({ defaultValue: false }),
}),
},
};
router.versioned
.post({
access: 'internal',
path: FetchSnapshotTelemetry,
})
// Just because it used to be /v2/, we are creating identical v1 and v2.
.addVersion({ version: '1', validate: v2Validations }, v2Handler)
.addVersion({ version: '2', validate: v2Validations }, v2Handler);
}

View file

@ -7,6 +7,9 @@
*/
import type { IRouter } from '@kbn/core/server';
import { RequestHandler } from '@kbn/core-http-server';
import { RequestHandlerContext } from '@kbn/core/server';
import { UserHasSeenNoticeRoute } from '../../common/routes';
import { TELEMETRY_SAVED_OBJECT_TYPE } from '../saved_objects';
import { v2 } from '../../common/types';
import {
@ -16,38 +19,42 @@ import {
} from '../saved_objects';
export function registerTelemetryUserHasSeenNotice(router: IRouter, currentKibanaVersion: string) {
router.put(
{
path: '/api/telemetry/v2/userHasSeenNotice',
validate: false,
},
async (context, req, res) => {
const soClient = (await context.core).savedObjects.getClient({
includedHiddenTypes: [TELEMETRY_SAVED_OBJECT_TYPE],
});
const telemetrySavedObject = await getTelemetrySavedObject(soClient);
const v2Handler: RequestHandler<undefined, undefined, undefined, RequestHandlerContext> = async (
context,
req,
res
) => {
const soClient = (await context.core).savedObjects.getClient({
includedHiddenTypes: [TELEMETRY_SAVED_OBJECT_TYPE],
});
const telemetrySavedObject = await getTelemetrySavedObject(soClient);
// update the object with a flag stating that the opt-in notice has been seen
const updatedAttributes: TelemetrySavedObjectAttributes = {
...telemetrySavedObject,
userHasSeenNotice: true,
// We need to store that the user was notified in this version.
// Otherwise, it'll continuously show the banner if previously opted-out.
lastVersionChecked: currentKibanaVersion,
};
await updateTelemetrySavedObject(soClient, updatedAttributes);
// update the object with a flag stating that the opt-in notice has been seen
const updatedAttributes: TelemetrySavedObjectAttributes = {
...telemetrySavedObject,
userHasSeenNotice: true,
// We need to store that the user was notified in this version.
// Otherwise, it'll continuously show the banner if previously opted-out.
lastVersionChecked: currentKibanaVersion,
};
await updateTelemetrySavedObject(soClient, updatedAttributes);
const body: v2.Telemetry = {
allowChangingOptInStatus: updatedAttributes.allowChangingOptInStatus,
enabled: updatedAttributes.enabled,
lastReported: updatedAttributes.lastReported,
lastVersionChecked: updatedAttributes.lastVersionChecked,
reportFailureCount: updatedAttributes.reportFailureCount,
reportFailureVersion: updatedAttributes.reportFailureVersion,
sendUsageFrom: updatedAttributes.sendUsageFrom,
userHasSeenNotice: updatedAttributes.userHasSeenNotice,
};
return res.ok({ body });
}
);
const body: v2.Telemetry = {
allowChangingOptInStatus: updatedAttributes.allowChangingOptInStatus,
enabled: updatedAttributes.enabled,
lastReported: updatedAttributes.lastReported,
lastVersionChecked: updatedAttributes.lastVersionChecked,
reportFailureCount: updatedAttributes.reportFailureCount,
reportFailureVersion: updatedAttributes.reportFailureVersion,
sendUsageFrom: updatedAttributes.sendUsageFrom,
userHasSeenNotice: updatedAttributes.userHasSeenNotice,
};
return res.ok({ body });
};
router.versioned
.put({ access: 'internal', path: UserHasSeenNoticeRoute })
// Just because it used to be /v2/, we are creating identical v1 and v2.
.addVersion({ version: '1', validate: false }, v2Handler)
.addVersion({ version: '2', validate: false }, v2Handler);
}

View file

@ -34,6 +34,7 @@
"@kbn/std",
"@kbn/core-http-browser-mocks",
"@kbn/core-http-browser",
"@kbn/core-http-server",
],
"exclude": [
"target/**/*",

View file

@ -11,6 +11,10 @@ import expect from '@kbn/expect';
import SuperTest from 'supertest';
import type { KbnClient } from '@kbn/test';
import type { TelemetrySavedObjectAttributes } from '@kbn/telemetry-plugin/server/saved_objects';
import {
ELASTIC_HTTP_VERSION_HEADER,
X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
} from '@kbn/core-http-common';
import type { FtrProviderContext } from '../../ftr_provider_context';
export default function optInTest({ getService }: FtrProviderContext) {
@ -18,7 +22,7 @@ export default function optInTest({ getService }: FtrProviderContext) {
const kibanaServer = getService('kibanaServer');
const esArchiver = getService('esArchiver');
describe('/api/telemetry/v2/optIn API', () => {
describe('/internal/telemetry/optIn API', () => {
let defaultAttributes: TelemetrySavedObjectAttributes;
let kibanaVersion: string;
before(async () => {
@ -88,8 +92,10 @@ async function postTelemetryV2OptIn(
statusCode: number
): Promise<any> {
const { body } = await supertest
.post('/api/telemetry/v2/optIn')
.post('/internal/telemetry/optIn')
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({ enabled: value })
.expect(statusCode);

View file

@ -7,6 +7,10 @@
*/
import { AxiosError } from 'axios';
import {
ELASTIC_HTTP_VERSION_HEADER,
X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
} from '@kbn/core-http-common';
import type { FtrProviderContext } from '../../ftr_provider_context';
const TELEMETRY_SO_TYPE = 'telemetry';
@ -16,110 +20,146 @@ export default function telemetryConfigTest({ getService }: FtrProviderContext)
const kbnClient = getService('kibanaServer');
const supertest = getService('supertest');
describe('/api/telemetry/v2/config API Telemetry config', () => {
before(async () => {
try {
await kbnClient.savedObjects.delete({ type: TELEMETRY_SO_TYPE, id: TELEMETRY_SO_ID });
} catch (err) {
const is404Error = err instanceof AxiosError && err.response?.status === 404;
if (!is404Error) {
throw err;
}
}
});
it('GET should get the default config', async () => {
await supertest.get('/api/telemetry/v2/config').set('kbn-xsrf', 'xxx').expect(200, {
allowChangingOptInStatus: true,
optIn: null, // the config.js for this FTR sets it to `false`, we are bound to ask again.
sendUsageFrom: 'server',
telemetryNotifyUserAboutOptInDefault: false, // it's not opted-in by default (that's what this flag is about)
});
});
it('GET should get `true` when opted-in', async () => {
// Opt-in
await supertest
.post('/api/telemetry/v2/optIn')
.set('kbn-xsrf', 'xxx')
.send({ enabled: true })
.expect(200);
await supertest.get('/api/telemetry/v2/config').set('kbn-xsrf', 'xxx').expect(200, {
allowChangingOptInStatus: true,
optIn: true,
sendUsageFrom: 'server',
telemetryNotifyUserAboutOptInDefault: false,
});
});
it('GET should get false when opted-out', async () => {
// Opt-in
await supertest
.post('/api/telemetry/v2/optIn')
.set('kbn-xsrf', 'xxx')
.send({ enabled: false })
.expect(200);
await supertest.get('/api/telemetry/v2/config').set('kbn-xsrf', 'xxx').expect(200, {
allowChangingOptInStatus: true,
optIn: false,
sendUsageFrom: 'server',
telemetryNotifyUserAboutOptInDefault: false,
});
});
describe('From a previous version', function () {
this.tags(['skipCloud']);
// Get current values
let attributes: Record<string, unknown>;
let currentVersion: string;
let previousMinor: string;
before(async () => {
[{ attributes }, currentVersion] = await Promise.all([
kbnClient.savedObjects.get({ type: TELEMETRY_SO_TYPE, id: TELEMETRY_SO_ID }),
kbnClient.version.get(),
]);
const [major, minor, patch] = currentVersion.match(/^(\d+)\.(\d+)\.(\d+)/)!.map(parseInt);
previousMinor = `${minor === 0 ? major - 1 : major}.${
minor === 0 ? minor : minor - 1
}.${patch}`;
});
it('GET should get `true` when opted-in in the current version', async () => {
// Opt-in from a previous version
await kbnClient.savedObjects.create({
overwrite: true,
type: TELEMETRY_SO_TYPE,
id: TELEMETRY_SO_ID,
attributes: { ...attributes, enabled: true, lastVersionChecked: previousMinor },
describe('API Telemetry config', () => {
['/api/telemetry/v2/config', '/internal/telemetry/config'].forEach((api) => {
describe(`GET ${api}`, () => {
const apiVersion = api === '/api/telemetry/v2/config' ? '2023-10-31' : '2';
before(async () => {
try {
await kbnClient.savedObjects.delete({ type: TELEMETRY_SO_TYPE, id: TELEMETRY_SO_ID });
} catch (err) {
const is404Error = err instanceof AxiosError && err.response?.status === 404;
if (!is404Error) {
throw err;
}
}
});
await supertest.get('/api/telemetry/v2/config').set('kbn-xsrf', 'xxx').expect(200, {
allowChangingOptInStatus: true,
optIn: true,
sendUsageFrom: 'server',
telemetryNotifyUserAboutOptInDefault: false,
});
});
it('GET should get `null` when opted-out in a previous version', async () => {
// Opt-out from previous version
await kbnClient.savedObjects.create({
overwrite: true,
type: TELEMETRY_SO_TYPE,
id: TELEMETRY_SO_ID,
attributes: { ...attributes, enabled: false, lastVersionChecked: previousMinor },
it('GET should get the default config', async () => {
await supertest
.get(api)
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, apiVersion)
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.expect(200, {
allowChangingOptInStatus: true,
optIn: null, // the config.js for this FTR sets it to `false`, we are bound to ask again.
sendUsageFrom: 'server',
telemetryNotifyUserAboutOptInDefault: false, // it's not opted-in by default (that's what this flag is about)
});
});
await supertest.get('/api/telemetry/v2/config').set('kbn-xsrf', 'xxx').expect(200, {
allowChangingOptInStatus: true,
optIn: null,
sendUsageFrom: 'server',
telemetryNotifyUserAboutOptInDefault: false,
it('GET should get `true` when opted-in', async () => {
// Opt-in
await supertest
.post('/internal/telemetry/optIn')
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({ enabled: true })
.expect(200);
await supertest
.get(api)
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, apiVersion)
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.expect(200, {
allowChangingOptInStatus: true,
optIn: true,
sendUsageFrom: 'server',
telemetryNotifyUserAboutOptInDefault: false,
});
});
it('GET should get false when opted-out', async () => {
// Opt-in
await supertest
.post('/internal/telemetry/optIn')
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({ enabled: false })
.expect(200);
await supertest
.get(api)
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, apiVersion)
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.expect(200, {
allowChangingOptInStatus: true,
optIn: false,
sendUsageFrom: 'server',
telemetryNotifyUserAboutOptInDefault: false,
});
});
describe('From a previous version', function () {
this.tags(['skipCloud']);
// Get current values
let attributes: Record<string, unknown>;
let currentVersion: string;
let previousMinor: string;
before(async () => {
[{ attributes }, currentVersion] = await Promise.all([
kbnClient.savedObjects.get({ type: TELEMETRY_SO_TYPE, id: TELEMETRY_SO_ID }),
kbnClient.version.get(),
]);
const [major, minor, patch] = currentVersion
.match(/^(\d+)\.(\d+)\.(\d+)/)!
.map(parseInt);
previousMinor = `${minor === 0 ? major - 1 : major}.${
minor === 0 ? minor : minor - 1
}.${patch}`;
});
it('GET should get `true` when opted-in in the current version', async () => {
// Opt-in from a previous version
await kbnClient.savedObjects.create({
overwrite: true,
type: TELEMETRY_SO_TYPE,
id: TELEMETRY_SO_ID,
attributes: { ...attributes, enabled: true, lastVersionChecked: previousMinor },
});
await supertest
.get(api)
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, apiVersion)
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.expect(200, {
allowChangingOptInStatus: true,
optIn: true,
sendUsageFrom: 'server',
telemetryNotifyUserAboutOptInDefault: false,
});
});
it('GET should get `null` when opted-out in a previous version', async () => {
// Opt-out from previous version
await kbnClient.savedObjects.create({
overwrite: true,
type: TELEMETRY_SO_TYPE,
id: TELEMETRY_SO_ID,
attributes: { ...attributes, enabled: false, lastVersionChecked: previousMinor },
});
await supertest
.get(api)
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, apiVersion)
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.expect(200, {
allowChangingOptInStatus: true,
optIn: null,
sendUsageFrom: 'server',
telemetryNotifyUserAboutOptInDefault: false,
});
});
});
});
});

View file

@ -7,23 +7,37 @@
*/
import expect from '@kbn/expect';
import {
ELASTIC_HTTP_VERSION_HEADER,
X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
} from '@kbn/core-http-common';
import type { FtrProviderContext } from '../../ftr_provider_context';
export default function optInTest({ getService }: FtrProviderContext) {
const client = getService('kibanaServer');
const supertest = getService('supertest');
describe('/api/telemetry/v2/last_reported API Telemetry lastReported', () => {
describe('/internal/telemetry/last_reported API Telemetry lastReported', () => {
before(async () => {
await client.savedObjects.delete({ type: 'telemetry', id: 'telemetry' });
});
it('GET should return undefined when there is no stored telemetry.lastReported value', async () => {
await supertest.get('/api/telemetry/v2/last_reported').set('kbn-xsrf', 'xxx').expect(200, {});
await supertest
.get('/internal/telemetry/last_reported')
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.expect(200, {});
});
it('PUT should update telemetry.lastReported to now', async () => {
await supertest.put('/api/telemetry/v2/last_reported').set('kbn-xsrf', 'xxx').expect(200);
await supertest
.put('/internal/telemetry/last_reported')
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.expect(200);
const {
attributes: { lastReported },
@ -46,8 +60,10 @@ export default function optInTest({ getService }: FtrProviderContext) {
expect(lastReported).to.be.a('number');
await supertest
.get('/api/telemetry/v2/last_reported')
.get('/internal/telemetry/last_reported')
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.expect(200, { lastReported });
});
});

View file

@ -7,17 +7,26 @@
*/
import expect from '@kbn/expect';
import {
ELASTIC_HTTP_VERSION_HEADER,
X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
} from '@kbn/core-http-common';
import type { FtrProviderContext } from '../../ftr_provider_context';
export default function optInTest({ getService }: FtrProviderContext) {
const client = getService('kibanaServer');
const supertest = getService('supertest');
describe('/api/telemetry/v2/userHasSeenNotice API Telemetry User has seen OptIn Notice', () => {
describe('/internal/telemetry/userHasSeenNotice API Telemetry User has seen OptIn Notice', () => {
it('should update telemetry setting field via PUT', async () => {
await client.savedObjects.delete({ type: 'telemetry', id: 'telemetry' });
await supertest.put('/api/telemetry/v2/userHasSeenNotice').set('kbn-xsrf', 'xxx').expect(200);
await supertest
.put('/internal/telemetry/userHasSeenNotice')
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.expect(200);
const {
attributes: { userHasSeenNotice },

View file

@ -8,6 +8,10 @@
import expect from '@kbn/expect';
import { KBN_SCREENSHOT_MODE_ENABLED_KEY } from '@kbn/screenshot-mode-plugin/public';
import {
ELASTIC_HTTP_VERSION_HEADER,
X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
} from '@kbn/core-http-common';
import { PluginFunctionalProviderContext } from '../../services';
const TELEMETRY_SO_TYPE = 'telemetry';
@ -83,8 +87,10 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
it('does not show the banner if opted-in', async () => {
await supertest
.post('/api/telemetry/v2/optIn')
.post('/internal/telemetry/optIn')
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({ enabled: true })
.expect(200);
@ -95,8 +101,10 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
it('does not show the banner if opted-out in this version', async () => {
await supertest
.post('/api/telemetry/v2/optIn')
.post('/internal/telemetry/optIn')
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({ enabled: false })
.expect(200);

View file

@ -8,6 +8,6 @@
export const name = 'telemetry';
export const description = 'Get the clusters stats from the Kibana server';
export const method = 'POST';
export const path = '/api/telemetry/v2/clusters/_stats';
export const path = '/internal/telemetry/clusters/_stats';
export const body = { unencrypted: true, refreshCache: true };

View file

@ -8,6 +8,10 @@
import expect from '@kbn/expect';
import { SuperTest } from 'supertest';
import { CSV_QUOTE_VALUES_SETTING } from '@kbn/share-plugin/common/constants';
import {
ELASTIC_HTTP_VERSION_HEADER,
X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
} from '@kbn/core-http-common';
import { FtrProviderContext } from '../../../ftr_provider_context';
export default function featureControlsTests({ getService }: FtrProviderContext) {
@ -64,9 +68,11 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
const basePath = spaceId ? `/s/${spaceId}` : '';
return await supertest
.post(`${basePath}/api/telemetry/v2/optIn`)
.post(`${basePath}/internal/telemetry/optIn`)
.auth(username, password)
.set('kbn-xsrf', 'foo')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({ enabled: true })
.then((response: any) => ({ error: undefined, response }))
.catch((error: any) => ({ error, response: undefined }));

View file

@ -7,6 +7,10 @@
import expect from '@kbn/expect';
import { estypes } from '@elastic/elasticsearch';
import {
ELASTIC_HTTP_VERSION_HEADER,
X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
} from '@kbn/core-http-common';
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getService }: FtrProviderContext) {
@ -17,7 +21,9 @@ export default function ({ getService }: FtrProviderContext) {
const {
body: [{ stats: apiResponse }],
} = await supertest
.post(`/api/telemetry/v2/clusters/_stats`)
.post(`/internal/telemetry/clusters/_stats`)
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.set('kbn-xsrf', 'xxxx')
.send({
unencrypted: true,

View file

@ -21,6 +21,10 @@ import type {
CacheDetails,
} from '@kbn/telemetry-collection-manager-plugin/server/types';
import { assertTelemetryPayload } from '@kbn/telemetry-tools';
import {
ELASTIC_HTTP_VERSION_HEADER,
X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
} from '@kbn/core-http-common';
import basicClusterFixture from './fixtures/basiccluster.json';
import multiClusterFixture from './fixtures/multicluster.json';
import type { SecurityService } from '../../../../../test/common/services/security/security';
@ -97,7 +101,7 @@ export default function ({ getService }: FtrProviderContext) {
const esSupertest = getService('esSupertest');
const security = getService('security');
describe('/api/telemetry/v2/clusters/_stats', () => {
describe('/internal/telemetry/clusters/_stats', () => {
const timestamp = new Date().toISOString();
describe('monitoring/multicluster', () => {
let localXPack: Record<string, unknown>;
@ -112,8 +116,10 @@ export default function ({ getService }: FtrProviderContext) {
await updateMonitoringDates(esSupertest, fromTimestamp, toTimestamp, timestamp);
const { body }: { body: UnencryptedTelemetryPayload } = await supertest
.post('/api/telemetry/v2/clusters/_stats')
.post('/internal/telemetry/clusters/_stats')
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({ unencrypted: true, refreshCache: true })
.expect(200);
@ -167,8 +173,10 @@ export default function ({ getService }: FtrProviderContext) {
after(() => esArchiver.unload(archive));
it('should load non-expiring basic cluster', async () => {
const { body }: { body: UnencryptedTelemetryPayload } = await supertest
.post('/api/telemetry/v2/clusters/_stats')
.post('/internal/telemetry/clusters/_stats')
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({ unencrypted: true, refreshCache: true })
.expect(200);
@ -193,8 +201,10 @@ export default function ({ getService }: FtrProviderContext) {
await updateMonitoringDates(esSupertest, fromTimestamp, toTimestamp, timestamp);
// hit the endpoint to cache results
await supertest
.post('/api/telemetry/v2/clusters/_stats')
.post('/internal/telemetry/clusters/_stats')
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({ unencrypted: true, refreshCache: true })
.expect(200);
});
@ -204,8 +214,10 @@ export default function ({ getService }: FtrProviderContext) {
it('returns non-cached results when unencrypted', async () => {
const now = Date.now();
const { body }: { body: UnencryptedTelemetryPayload } = await supertest
.post('/api/telemetry/v2/clusters/_stats')
.post('/internal/telemetry/clusters/_stats')
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({ unencrypted: true })
.expect(200);
@ -224,8 +236,10 @@ export default function ({ getService }: FtrProviderContext) {
it('grabs a fresh copy on refresh', async () => {
const now = Date.now();
const { body }: { body: UnencryptedTelemetryPayload } = await supertest
.post('/api/telemetry/v2/clusters/_stats')
.post('/internal/telemetry/clusters/_stats')
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({ unencrypted: true, refreshCache: true })
.expect(200);
@ -243,16 +257,20 @@ export default function ({ getService }: FtrProviderContext) {
describe('superadmin user', () => {
it('should return unencrypted telemetry for the admin user', async () => {
await supertest
.post('/api/telemetry/v2/clusters/_stats')
.post('/internal/telemetry/clusters/_stats')
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({ unencrypted: true })
.expect(200);
});
it('should return encrypted telemetry for the admin user', async () => {
await supertest
.post('/api/telemetry/v2/clusters/_stats')
.post('/internal/telemetry/clusters/_stats')
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({ unencrypted: false })
.expect(200);
});
@ -281,18 +299,22 @@ export default function ({ getService }: FtrProviderContext) {
it('should return encrypted telemetry for the global-read user', async () => {
await supertestWithoutAuth
.post('/api/telemetry/v2/clusters/_stats')
.post('/internal/telemetry/clusters/_stats')
.auth(globalReadOnlyUser, password(globalReadOnlyUser))
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({ unencrypted: false })
.expect(200);
});
it('should return unencrypted telemetry for the global-read user', async () => {
await supertestWithoutAuth
.post('/api/telemetry/v2/clusters/_stats')
.post('/internal/telemetry/clusters/_stats')
.auth(globalReadOnlyUser, password(globalReadOnlyUser))
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({ unencrypted: true })
.expect(200);
});
@ -330,18 +352,22 @@ export default function ({ getService }: FtrProviderContext) {
it('should return encrypted telemetry for the read-only user', async () => {
await supertestWithoutAuth
.post('/api/telemetry/v2/clusters/_stats')
.post('/internal/telemetry/clusters/_stats')
.auth(noGlobalUser, password(noGlobalUser))
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({ unencrypted: false })
.expect(200);
});
it('should return 403 when the read-only user requests unencrypted telemetry', async () => {
await supertestWithoutAuth
.post('/api/telemetry/v2/clusters/_stats')
.post('/internal/telemetry/clusters/_stats')
.auth(noGlobalUser, password(noGlobalUser))
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({ unencrypted: true })
.expect(403);
});

View file

@ -12,6 +12,10 @@ import ossPluginsTelemetrySchema from '@kbn/telemetry-plugin/schema/oss_plugins.
import xpackRootTelemetrySchema from '@kbn/telemetry-collection-xpack-plugin/schema/xpack_root.json';
import xpackPluginsTelemetrySchema from '@kbn/telemetry-collection-xpack-plugin/schema/xpack_plugins.json';
import { assertTelemetryPayload } from '@kbn/telemetry-tools';
import {
ELASTIC_HTTP_VERSION_HEADER,
X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
} from '@kbn/core-http-common';
import { flatKeys } from '../../../../../test/api_integration/apis/telemetry/utils';
import type { FtrProviderContext } from '../../ftr_provider_context';
@ -31,7 +35,7 @@ export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const es = getService('es');
describe('/api/telemetry/v2/clusters/_stats with monitoring disabled', () => {
describe('/internal/telemetry/clusters/_stats with monitoring disabled', () => {
let stats: Record<string, any>;
before('disable monitoring and pull local stats', async () => {
@ -39,8 +43,10 @@ export default function ({ getService }: FtrProviderContext) {
await new Promise((r) => setTimeout(r, 1000));
const { body } = await supertest
.post('/api/telemetry/v2/clusters/_stats')
.post('/internal/telemetry/clusters/_stats')
.set('kbn-xsrf', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({ unencrypted: true, refreshCache: true })
.expect(200);

View file

@ -6,6 +6,10 @@
*/
import { UsageStatsPayload } from '@kbn/telemetry-collection-manager-plugin/server';
import {
ELASTIC_HTTP_VERSION_HEADER,
X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
} from '@kbn/core-http-common';
import { FtrProviderContext } from '../ftr_provider_context';
export interface UsageStatsPayloadTestFriendly extends UsageStatsPayload {
@ -29,9 +33,10 @@ export function UsageAPIProvider({ getService }: FtrProviderContext) {
refreshCache?: boolean;
}): Promise<Array<{ clusterUuid: string; stats: UsageStatsPayloadTestFriendly | string }>> {
const { body } = await supertest
.post('/api/telemetry/v2/clusters/_stats')
.post('/internal/telemetry/clusters/_stats')
.set('kbn-xsrf', 'xxx')
.set('x-elastic-internal-origin', 'xxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({ refreshCache: true, ...payload })
.expect(200);
return body;

View file

@ -6,7 +6,10 @@
*/
import expect from '@kbn/expect';
import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
import {
ELASTIC_HTTP_VERSION_HEADER,
X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
} from '@kbn/core-http-common';
import { data, MockTelemetryFindings } from './data';
import type { FtrProviderContext } from '../ftr_provider_context';
@ -67,7 +70,9 @@ export default function ({ getService }: FtrProviderContext) {
const {
body: [{ stats: apiResponse }],
} = await supertest
.post(`/api/telemetry/v2/clusters/_stats`)
.post(`/internal/telemetry/clusters/_stats`)
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.set('kbn-xsrf', 'xxxx')
.send({
unencrypted: true,
@ -119,8 +124,10 @@ export default function ({ getService }: FtrProviderContext) {
const {
body: [{ stats: apiResponse }],
} = await supertest
.post(`/api/telemetry/v2/clusters/_stats`)
.post(`/internal/telemetry/clusters/_stats`)
.set('kbn-xsrf', 'xxxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({
unencrypted: true,
refreshCache: true,
@ -164,8 +171,10 @@ export default function ({ getService }: FtrProviderContext) {
const {
body: [{ stats: apiResponse }],
} = await supertest
.post(`/api/telemetry/v2/clusters/_stats`)
.post(`/internal/telemetry/clusters/_stats`)
.set('kbn-xsrf', 'xxxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({
unencrypted: true,
refreshCache: true,
@ -240,8 +249,10 @@ export default function ({ getService }: FtrProviderContext) {
const {
body: [{ stats: apiResponse }],
} = await supertest
.post(`/api/telemetry/v2/clusters/_stats`)
.post(`/internal/telemetry/clusters/_stats`)
.set('kbn-xsrf', 'xxxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({
unencrypted: true,
refreshCache: true,
@ -294,8 +305,10 @@ export default function ({ getService }: FtrProviderContext) {
const {
body: [{ stats: apiResponse }],
} = await supertest
.post(`/api/telemetry/v2/clusters/_stats`)
.post(`/internal/telemetry/clusters/_stats`)
.set('kbn-xsrf', 'xxxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({
unencrypted: true,
refreshCache: true,

View file

@ -8,6 +8,10 @@
import type { ToolingLog } from '@kbn/tooling-log';
import type SuperTest from 'supertest';
import type { DetectionMetrics } from '@kbn/security-solution-plugin/server/usage/detections/types';
import {
ELASTIC_HTTP_VERSION_HEADER,
X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
} from '@kbn/core-http-common';
import { getStatsUrl } from './get_stats_url';
import { getDetectionMetricsFromBody } from './get_detection_metrics_from_body';
@ -24,6 +28,8 @@ export const getStats = async (
const response = await supertest
.post(getStatsUrl())
.set('kbn-xsrf', 'true')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({ unencrypted: true, refreshCache: true });
if (response.status !== 200) {
log.error(

View file

@ -8,4 +8,4 @@
/**
* Cluster stats URL. Replace this with any from kibana core if there is ever a constant there for this.
*/
export const getStatsUrl = (): string => '/api/telemetry/v2/clusters/_stats';
export const getStatsUrl = (): string => '/internal/telemetry/clusters/_stats';

View file

@ -5,6 +5,10 @@
* 2.0.
*/
import {
ELASTIC_HTTP_VERSION_HEADER,
X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
} from '@kbn/core-http-common';
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../api_integration/ftr_provider_context';
import { skipIfNoDockerRegistry, generateAgent } from '../helpers';
@ -124,8 +128,10 @@ export default function (providerContext: FtrProviderContext) {
const {
body: [{ stats: apiResponse }],
} = await supertest
.post(`/api/telemetry/v2/clusters/_stats`)
.post(`/internal/telemetry/clusters/_stats`)
.set('kbn-xsrf', 'xxxx')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({
unencrypted: true,
refreshCache: true,

View file

@ -6,6 +6,10 @@
*/
import expect from '@kbn/expect';
import {
ELASTIC_HTTP_VERSION_HEADER,
X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
} from '@kbn/core-http-common';
import { DATES } from './constants';
import { FtrProviderContext } from '../../ftr_provider_context';
@ -133,8 +137,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await logsUi.logStreamPage.getStreamEntries();
const [{ stats }] = await supertest
.post(`/api/telemetry/v2/clusters/_stats`)
.post(`/internal/telemetry/clusters/_stats`)
.set(COMMON_REQUEST_HEADERS)
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.set('Accept', 'application/json')
.send({
unencrypted: true,

View file

@ -5,6 +5,10 @@
* 2.0.
*/
import {
ELASTIC_HTTP_VERSION_HEADER,
X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
} from '@kbn/core-http-common';
import expect from '@kbn/expect';
import type { FtrProviderContext } from '../ftr_provider_context';
import { assertLogContains, isExecutionContextLog, ANY } from '../test_utils';
@ -111,8 +115,10 @@ export default function ({ getService }: FtrProviderContext) {
it('propagates context for Telemetry collection', async () => {
await supertest
.post('/api/telemetry/v2/clusters/_stats')
.post('/internal/telemetry/clusters/_stats')
.set('kbn-xsrf', 'true')
.set(ELASTIC_HTTP_VERSION_HEADER, '2')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({ unencrypted: false })
.expect(200);

View file

@ -1,5 +1,5 @@
{
"journeyName": "POST /api/telemetry/v2/clusters/_stats - 1600 dataviews",
"journeyName": "POST /internal/telemetry/clusters/_stats - 1600 dataviews",
"scalabilitySetup": {
"warmup": [
{
@ -30,13 +30,15 @@
{
"http": {
"method": "POST",
"path": "/api/telemetry/v2/clusters/_stats",
"path": "/internal/telemetry/clusters/_stats",
"body": "{}",
"headers": {
"Cookie": "",
"Kbn-Version": "",
"Accept-Encoding": "gzip, deflate, br",
"Content-Type": "application/json"
"Content-Type": "application/json",
"elastic-api-version": "2",
"x-elastic-internal-origin": "kibana"
},
"statusCode": 200
}

View file

@ -1,5 +1,5 @@
{
"journeyName": "POST /api/telemetry/v2/clusters/_stats",
"journeyName": "POST /internal/telemetry/clusters/_stats",
"scalabilitySetup": {
"warmup": [
{
@ -28,13 +28,15 @@
{
"http": {
"method": "POST",
"path": "/api/telemetry/v2/clusters/_stats",
"path": "/internal/telemetry/clusters/_stats",
"body": "{}",
"headers": {
"Cookie": "",
"Kbn-Version": "",
"Accept-Encoding": "gzip, deflate, br",
"Content-Type": "application/json"
"Content-Type": "application/json",
"elastic-api-version": "2",
"x-elastic-internal-origin": "kibana"
},
"statusCode": 200
}

View file

@ -1,5 +1,5 @@
{
"journeyName": "POST /api/telemetry/v2/clusters/_stats - no cache - 1600 dataviews",
"journeyName": "POST /internal/telemetry/clusters/_stats - no cache - 1600 dataviews",
"scalabilitySetup": {
"responseTimeThreshold": {
"threshold1": 1000,
@ -35,13 +35,15 @@
{
"http": {
"method": "POST",
"path": "/api/telemetry/v2/clusters/_stats",
"path": "/internal/telemetry/clusters/_stats",
"body": "{ \"refreshCache\": true }",
"headers": {
"Cookie": "",
"Kbn-Version": "",
"Accept-Encoding": "gzip, deflate, br",
"Content-Type": "application/json"
"Content-Type": "application/json",
"elastic-api-version": "2",
"x-elastic-internal-origin": "kibana"
},
"timeout": 240000,
"statusCode": 200

View file

@ -1,5 +1,5 @@
{
"journeyName": "POST /api/telemetry/v2/clusters/_stats - no cache",
"journeyName": "POST /internal/telemetry/clusters/_stats - no cache",
"scalabilitySetup": {
"responseTimeThreshold": {
"threshold1": 1000,
@ -33,13 +33,15 @@
{
"http": {
"method": "POST",
"path": "/api/telemetry/v2/clusters/_stats",
"path": "/internal/telemetry/clusters/_stats",
"body": "{ \"refreshCache\": true }",
"headers": {
"Cookie": "",
"Kbn-Version": "",
"Accept-Encoding": "gzip, deflate, br",
"Content-Type": "application/json"
"Content-Type": "application/json",
"elastic-api-version": "2",
"x-elastic-internal-origin": "kibana"
},
"statusCode": 200
}