From 57eaba252a2ec31c2895163c00af4386d6b0de4b Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Mon, 14 Jun 2021 13:56:32 +0300 Subject: [PATCH 01/36] add execution context service on the server-side --- .../execution_context_client.ts | 37 +++++++++++++++++ .../execution_context_service.mock.ts | 30 ++++++++++++++ .../execution_context_service.ts | 41 +++++++++++++++++++ src/core/server/execution_context/index.ts | 11 +++++ src/core/types/execution_context.ts | 25 +++++++++++ src/core/types/index.ts | 1 + 6 files changed, 145 insertions(+) create mode 100644 src/core/server/execution_context/execution_context_client.ts create mode 100644 src/core/server/execution_context/execution_context_service.mock.ts create mode 100644 src/core/server/execution_context/execution_context_service.ts create mode 100644 src/core/server/execution_context/index.ts create mode 100644 src/core/types/execution_context.ts diff --git a/src/core/server/execution_context/execution_context_client.ts b/src/core/server/execution_context/execution_context_client.ts new file mode 100644 index 00000000000000..a008fbd4735d2f --- /dev/null +++ b/src/core/server/execution_context/execution_context_client.ts @@ -0,0 +1,37 @@ +/* + * 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. + */ +import { AsyncLocalStorage } from 'async_hooks'; +import type { KibanaExecutionContext } from '../../types'; + +/** @internal */ +export interface KibanaServerExecutionContext { + readonly requestId: string; + readonly parentContext?: KibanaExecutionContext; +} + +export interface IExecutionContext { + startWith(context: KibanaServerExecutionContext): void; + stop(): void; + get(): KibanaServerExecutionContext | undefined; +} + +export class ExecutionContextClient implements IExecutionContext { + private readonly asyncLocalStorage = new AsyncLocalStorage(); + startWith(context: KibanaServerExecutionContext) { + // we have to use enterWith since Hapi lifecycle model is built on event emitters. + // therefore if we wrapped request handler in asyncLocalStorage.run(), we would lose context in other lifecycles. + this.asyncLocalStorage.enterWith(context); + } + stop() { + // @ts-expect-error "undefined" is not supported in type definitions, which is wrong + this.asyncLocalStorage.enterWith(undefined); + } + get(): KibanaServerExecutionContext | undefined { + return this.asyncLocalStorage.getStore(); + } +} diff --git a/src/core/server/execution_context/execution_context_service.mock.ts b/src/core/server/execution_context/execution_context_service.mock.ts new file mode 100644 index 00000000000000..bb32d4b94ae551 --- /dev/null +++ b/src/core/server/execution_context/execution_context_service.mock.ts @@ -0,0 +1,30 @@ +/* + * 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. + */ + +import type { ExecutionContextSetup } from './execution_context_service'; +import type { IExecutionContext } from './execution_context_client'; + +const createExecutionContextClientMock = () => { + const clientMock: jest.Mocked = { + startWith: jest.fn(), + stop: jest.fn(), + get: jest.fn(), + }; + return clientMock; +}; +const createSetupContractMock = () => { + const setupContract: jest.Mocked = { + client: createExecutionContextClientMock(), + }; + return setupContract; +}; + +export const executionContextServiceMock = { + createClient: createExecutionContextClientMock, + createInternalSetupContract: createSetupContractMock, +}; diff --git a/src/core/server/execution_context/execution_context_service.ts b/src/core/server/execution_context/execution_context_service.ts new file mode 100644 index 00000000000000..c41416ab18a92c --- /dev/null +++ b/src/core/server/execution_context/execution_context_service.ts @@ -0,0 +1,41 @@ +/* + * 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. + */ +import type { CoreService } from '../../types'; +import type { CoreContext } from '../core_context'; +import type { Logger } from '../logging'; +import type { IExecutionContext } from './execution_context_client'; +import { ExecutionContextClient } from './execution_context_client'; + +// TODO rename to Internal +export interface ExecutionContextSetup { + client: IExecutionContext; +} + +// TODO remove if not usedConfig +export interface ExecutionContextStart { + client: IExecutionContext; +} + +export class ExecutionContextService implements CoreService { + private readonly client: ExecutionContextClient; + private readonly log: Logger; + constructor(coreContext: CoreContext) { + this.log = coreContext.logger.get('execution_context'); + // TODO add a config option to disable the service + this.client = new ExecutionContextClient(); + } + setup(): ExecutionContextSetup { + this.log.debug('setup execution context service'); + return { + client: this.client, + }; + } + // is it used? + start(): void {} + stop(): void | Promise {} +} diff --git a/src/core/server/execution_context/index.ts b/src/core/server/execution_context/index.ts new file mode 100644 index 00000000000000..9c0b15338f9f56 --- /dev/null +++ b/src/core/server/execution_context/index.ts @@ -0,0 +1,11 @@ +/* + * 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 { ExecutionContextService } from './execution_context_service'; +export type { ExecutionContextSetup, ExecutionContextStart } from './execution_context_service'; +export type { IExecutionContext } from './execution_context_client'; diff --git a/src/core/types/execution_context.ts b/src/core/types/execution_context.ts new file mode 100644 index 00000000000000..62d9a99f4dfcb8 --- /dev/null +++ b/src/core/types/execution_context.ts @@ -0,0 +1,25 @@ +/* + * 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. + */ + +/** @internal */ +export interface KibanaExecutionContext { + readonly requestId: string; + /** + * Kibana application initated an operation. + * Can be narrowed to an enum later. + * */ + readonly type: string; // 'visualization' | 'actions' | 'server' | ..; + /** public name of a user-facing feature */ + readonly name: string; // 'TSVB' | 'Lens' | 'action_execution' | ..; + /** unique value to indentify find the source */ + readonly id: string; + /** human readable description. For example, a vis title, action name */ + readonly description: string; + /** in browser - url to navigate to a current page, on server - endpoint path, for task: task SO url */ + readonly url?: string; +} diff --git a/src/core/types/index.ts b/src/core/types/index.ts index 21876844ed45b6..97f990f608c049 100644 --- a/src/core/types/index.ts +++ b/src/core/types/index.ts @@ -16,3 +16,4 @@ export * from './app_category'; export * from './ui_settings'; export * from './saved_objects'; export * from './serializable'; +export type { KibanaExecutionContext } from './execution_context'; From a720a6f4a9b3eb32cd2ec810ee820b2b0eebce7f Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Mon, 14 Jun 2021 13:58:04 +0300 Subject: [PATCH 02/36] integrate execution context service into http service --- src/core/server/http/http_server.ts | 29 ++++++++++++++++++++++++---- src/core/server/http/http_service.ts | 7 ++++++- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index 8b4c3b9416152f..a2a1683baf7a5c 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -22,6 +22,7 @@ import { Observable } from 'rxjs'; import { take } from 'rxjs/operators'; import { Logger, LoggerFactory } from '../logging'; import { HttpConfig } from './http_config'; +import type { ExecutionContextSetup } from '../execution_context'; import { adoptToHapiAuthFormat, AuthenticationHandler } from './lifecycle/auth'; import { adoptToHapiOnPreAuth, OnPreAuthHandler } from './lifecycle/on_pre_auth'; import { adoptToHapiOnPostAuthFormat, OnPostAuthHandler } from './lifecycle/on_post_auth'; @@ -133,19 +134,26 @@ export class HttpServer { } } - public async setup(config: HttpConfig): Promise { + public async setup( + config: HttpConfig, + executionContext?: ExecutionContextSetup + ): Promise { const serverOptions = getServerOptions(config); const listenerOptions = getListenerOptions(config); this.server = createServer(serverOptions, listenerOptions); await this.server.register([HapiStaticFiles]); this.config = config; + // It's important to have setupRequestStateAssignment call the very first, otherwise context passing will be broken. + // That's the only reason why context initialization exists in this method. + this.setupRequestStateAssignment(config, executionContext); const basePathService = new BasePath(config.basePath, config.publicBaseUrl); this.setupBasePathRewrite(config, basePathService); this.setupConditionalCompression(config); this.setupResponseLogging(); - this.setupRequestStateAssignment(config); this.setupGracefulShutdownHandlers(); + // cleanup context is the last operation to keep the context for the internal on('response' handlers. + this.setupContextExecutionCleanup(executionContext); return { registerRouter: this.registerRouter.bind(this), @@ -323,17 +331,30 @@ export class HttpServer { this.server.events.on('response', this.handleServerResponseEvent); } - private setupRequestStateAssignment(config: HttpConfig) { + private setupRequestStateAssignment( + config: HttpConfig, + executionContext?: ExecutionContextSetup + ) { this.server!.ext('onRequest', (request, responseToolkit) => { + const requestId = getRequestId(request, config.requestId); + executionContext?.client.startWith({ requestId }); + request.app = { ...(request.app ?? {}), - requestId: getRequestId(request, config.requestId), + requestId, requestUuid: uuid.v4(), } as KibanaRequestState; return responseToolkit.continue; }); } + private setupContextExecutionCleanup(executionContext?: ExecutionContextSetup) { + if (!executionContext) return; + this.server!.events.on('response', function () { + executionContext.client.stop(); + }); + } + private registerOnPreAuth(fn: OnPreAuthHandler) { if (this.server === undefined) { throw new Error('Server is not created yet'); diff --git a/src/core/server/http/http_service.ts b/src/core/server/http/http_service.ts index 0d28506607682e..4378e70f0f7dc0 100644 --- a/src/core/server/http/http_service.ts +++ b/src/core/server/http/http_service.ts @@ -11,6 +11,7 @@ import { first, map } from 'rxjs/operators'; import { pick } from '@kbn/std'; import type { RequestHandlerContext } from 'src/core/server'; +import type { ExecutionContextSetup } from '../execution_context'; import { CoreService } from '../../types'; import { Logger, LoggerFactory } from '../logging'; import { ContextSetup } from '../context'; @@ -41,6 +42,7 @@ import { interface SetupDeps { context: ContextSetup; + executionContext: ExecutionContextSetup; } /** @internal */ @@ -90,7 +92,10 @@ export class HttpService const notReadyServer = await this.setupNotReadyService({ config, context: deps.context }); - const { registerRouter, ...serverContract } = await this.httpServer.setup(config); + const { registerRouter, ...serverContract } = await this.httpServer.setup( + config, + deps.executionContext + ); registerCoreHandlers(serverContract, config, this.env); From 2233afd21bea877610b163ae0d4a3b0891175c99 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Mon, 14 Jun 2021 13:58:48 +0300 Subject: [PATCH 03/36] add integration tests for execution context + http server --- .../integration_tests/core_services.test.ts | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/src/core/server/http/integration_tests/core_services.test.ts b/src/core/server/http/integration_tests/core_services.test.ts index 99b63fc73687a1..af0b24f663c90a 100644 --- a/src/core/server/http/integration_tests/core_services.test.ts +++ b/src/core/server/http/integration_tests/core_services.test.ts @@ -408,4 +408,106 @@ describe('http service', () => { expect(body.message).toMatch('[error_type]: error_reason'); }); }); + + describe('execution context', () => { + let root: ReturnType; + beforeEach(async () => { + root = kbnTestServer.createRoot({ plugins: { initialize: false } }); + }, 30000); + + afterEach(async () => { + await root.shutdown(); + }); + + it('sets execution context for a request handler', async () => { + const { executionContext, http } = await root.setup(); + const { createRouter } = http; + + const router = createRouter(''); + router.get({ path: '/execution-context', validate: false }, (context, req, res) => + res.ok({ body: executionContext.client.get() }) + ); + + await root.start(); + const response = await kbnTestServer.request.get(root, '/execution-context').expect(200); + expect(response.body).toEqual({ requestId: expect.any(String) }); + expect(response.body.requestId).not.toHaveLength(0); + }); + + it('execution context is uniq for every request', async () => { + const { executionContext, http } = await root.setup(); + const { createRouter } = http; + + const router = createRouter(''); + router.get({ path: '/execution-context', validate: false }, (context, req, res) => + res.ok({ body: executionContext.client.get() }) + ); + + await root.start(); + const responseA = await kbnTestServer.request.get(root, '/execution-context').expect(200); + const responseB = await kbnTestServer.request.get(root, '/execution-context').expect(200); + + expect(responseA.body).toEqual({ requestId: expect.any(String) }); + expect(responseB.body).toEqual({ requestId: expect.any(String) }); + expect(responseA.body.requestId).not.toBe(responseB.body.requestId); + }); + + it('execution context is the same for all the lifecycle events', async () => { + const { executionContext, http } = await root.setup(); + const { + createRouter, + registerOnPreRouting, + registerOnPreAuth, + registerAuth, + registerOnPostAuth, + registerOnPreResponse, + } = http; + + const router = createRouter(''); + router.get({ path: '/execution-context', validate: false }, (context, req, res) => + res.ok({ body: executionContext.client.get() }) + ); + + let onPreRoutingContext; + registerOnPreRouting((request, response, t) => { + onPreRoutingContext = executionContext.client.get(); + return t.next(); + }); + + let onPreAuthContext; + registerOnPreAuth((request, response, t) => { + onPreAuthContext = executionContext.client.get(); + return t.next(); + }); + + let authContext; + registerAuth((request, response, t) => { + authContext = executionContext.client.get(); + return t.authenticated(); + }); + + let onPostAuthContext; + registerOnPostAuth((request, response, t) => { + onPostAuthContext = executionContext.client.get(); + return t.next(); + }); + + let onPreResponseContext; + registerOnPreResponse((request, response, t) => { + onPreResponseContext = executionContext.client.get(); + return t.next(); + }); + + await root.start(); + const response = await kbnTestServer.request.get(root, '/execution-context').expect(200); + + expect(response.body).toEqual({ requestId: expect.any(String) }); + + expect(response.body).toEqual(onPreRoutingContext); + expect(response.body).toEqual(onPreAuthContext); + expect(response.body).toEqual(authContext); + expect(response.body).toEqual(onPostAuthContext); + expect(response.body).toEqual(onPreResponseContext); + }); + }); }); From a495aa990cd002b08c43f6d1e08c5bd3df43d800 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Mon, 14 Jun 2021 13:59:41 +0300 Subject: [PATCH 04/36] update core code --- src/core/server/internal_types.ts | 2 ++ src/core/server/mocks.ts | 2 ++ src/core/server/saved_objects/routes/test_utils.ts | 2 ++ src/core/server/server.ts | 7 +++++++ 4 files changed, 13 insertions(+) diff --git a/src/core/server/internal_types.ts b/src/core/server/internal_types.ts index 34193f8d0c35e0..e3b8c858791ac8 100644 --- a/src/core/server/internal_types.ts +++ b/src/core/server/internal_types.ts @@ -30,6 +30,7 @@ import { InternalLoggingServiceSetup } from './logging'; import { CoreUsageDataStart } from './core_usage_data'; import { I18nServiceSetup } from './i18n'; import { InternalDeprecationsServiceSetup } from './deprecations'; +import type { ExecutionContextSetup } from './execution_context'; /** @internal */ export interface InternalCoreSetup { @@ -37,6 +38,7 @@ export interface InternalCoreSetup { context: ContextSetup; http: InternalHttpServiceSetup; elasticsearch: InternalElasticsearchServiceSetup; + executionContext: ExecutionContextSetup; i18n: I18nServiceSetup; savedObjects: InternalSavedObjectsServiceSetup; status: InternalStatusServiceSetup; diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 0d52ff64499c1c..0f131ff3a355fc 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -30,6 +30,7 @@ import { statusServiceMock } from './status/status_service.mock'; import { coreUsageDataServiceMock } from './core_usage_data/core_usage_data_service.mock'; import { i18nServiceMock } from './i18n/i18n_service.mock'; import { deprecationsServiceMock } from './deprecations/deprecations_service.mock'; +import { executionContextServiceMock } from './execution_context/execution_context_service.mock'; export { configServiceMock } from './config/mocks'; export { httpServerMock } from './http/http_server.mocks'; @@ -182,6 +183,7 @@ function createInternalCoreSetupMock() { logging: loggingServiceMock.createInternalSetupContract(), metrics: metricsServiceMock.createInternalSetupContract(), deprecations: deprecationsServiceMock.createInternalSetupContract(), + executionContext: executionContextServiceMock.createInternalSetupContract(), }; return setupDeps; } diff --git a/src/core/server/saved_objects/routes/test_utils.ts b/src/core/server/saved_objects/routes/test_utils.ts index e6826b118509e5..796bfd55b78279 100644 --- a/src/core/server/saved_objects/routes/test_utils.ts +++ b/src/core/server/saved_objects/routes/test_utils.ts @@ -9,6 +9,7 @@ import { ContextService } from '../../context'; import { createHttpServer, createCoreContext } from '../../http/test_utils'; import { coreMock } from '../../mocks'; +import { executionContextServiceMock } from '../../execution_context/execution_context_service.mock'; import { SavedObjectsType } from '../types'; const defaultCoreId = Symbol('core'); @@ -20,6 +21,7 @@ export const setupServer = async (coreId: symbol = defaultCoreId) => { const server = createHttpServer(coreContext); const httpSetup = await server.setup({ context: contextService.setup({ pluginDependencies: new Map() }), + executionContext: executionContextServiceMock.createInternalSetupContract(), }); const handlerContext = coreMock.createRequestHandlerContext(); diff --git a/src/core/server/server.ts b/src/core/server/server.ts index a31b9a061ac5d5..13047b319617dc 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -31,6 +31,7 @@ import { CapabilitiesService } from './capabilities'; import { EnvironmentService, config as pidConfig } from './environment'; // do not try to shorten the import to `./status`, it will break server test mocking import { StatusService } from './status/status_service'; +import { ExecutionContextService } from './execution_context'; import { config as cspConfig } from './csp'; import { config as elasticsearchConfig } from './elasticsearch'; @@ -73,6 +74,7 @@ export class Server { private readonly coreUsageData: CoreUsageDataService; private readonly i18n: I18nService; private readonly deprecations: DeprecationsService; + private readonly executionContext: ExecutionContextService; private readonly savedObjectsStartPromise: Promise; private resolveSavedObjectsStartPromise?: (value: SavedObjectsServiceStart) => void; @@ -109,6 +111,7 @@ export class Server { this.coreUsageData = new CoreUsageDataService(core); this.i18n = new I18nService(core); this.deprecations = new DeprecationsService(core); + this.executionContext = new ExecutionContextService(core); this.savedObjectsStartPromise = new Promise((resolve) => { this.resolveSavedObjectsStartPromise = resolve; @@ -133,9 +136,11 @@ export class Server { const contextServiceSetup = this.context.setup({ pluginDependencies: new Map([...pluginTree.asOpaqueIds]), }); + const executionContextSetup = this.executionContext.setup(); const httpSetup = await this.http.setup({ context: contextServiceSetup, + executionContext: executionContextSetup, }); // setup i18n prior to any other service, to have translations ready @@ -200,6 +205,7 @@ export class Server { context: contextServiceSetup, elasticsearch: elasticsearchServiceSetup, environment: environmentSetup, + executionContext: executionContextSetup, http: httpSetup, i18n: i18nServiceSetup, savedObjects: savedObjectsSetup, @@ -230,6 +236,7 @@ export class Server { this.log.debug('starting server'); const startTransaction = apm.startTransaction('server_start', 'kibana_platform'); + this.executionContext.start(); const elasticsearchStart = await this.elasticsearch.start(); const soStartSpan = startTransaction?.startSpan('saved_objects.migration', 'migration'); const savedObjectsStart = await this.savedObjects.start({ From abef41f761f4d8d3fe02cc29ded4705792f8cc81 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Mon, 14 Jun 2021 14:00:01 +0300 Subject: [PATCH 05/36] update integration tests --- .../integration_tests/capabilities_service.test.ts | 2 ++ .../core_app/integration_tests/bundle_routes.test.ts | 8 ++++++++ src/core/server/http/cookie_session_storage.test.ts | 2 ++ src/core/server/http/http_service.test.ts | 2 ++ src/core/server/http/integration_tests/lifecycle.test.ts | 2 ++ .../http/integration_tests/lifecycle_handlers.test.ts | 2 ++ src/core/server/http/integration_tests/request.test.ts | 2 ++ src/core/server/http/integration_tests/router.test.ts | 2 ++ .../metrics/integration_tests/server_collector.test.ts | 6 +++++- .../saved_objects/routes/integration_tests/get.test.ts | 2 ++ .../routes/integration_tests/resolve.test.ts | 2 ++ .../server/status/routes/integration_tests/status.test.ts | 2 ++ 12 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/core/server/capabilities/integration_tests/capabilities_service.test.ts b/src/core/server/capabilities/integration_tests/capabilities_service.test.ts index 4e4bc4a51a7a8f..ac793d960d03b8 100644 --- a/src/core/server/capabilities/integration_tests/capabilities_service.test.ts +++ b/src/core/server/capabilities/integration_tests/capabilities_service.test.ts @@ -10,6 +10,7 @@ import supertest from 'supertest'; import { REPO_ROOT } from '@kbn/dev-utils'; import { HttpService, InternalHttpServiceSetup } from '../../http'; import { contextServiceMock } from '../../context/context_service.mock'; +import { executionContextServiceMock } from '../../execution_context/execution_context_service.mock'; import { loggingSystemMock } from '../../logging/logging_system.mock'; import { Env } from '../../config'; import { getEnvOptions } from '../../config/mocks'; @@ -31,6 +32,7 @@ describe('CapabilitiesService', () => { server = createHttpServer(); httpSetup = await server.setup({ context: contextServiceMock.createSetupContract(), + executionContext: executionContextServiceMock.createInternalSetupContract(), }); service = new CapabilitiesService({ coreId, diff --git a/src/core/server/core_app/integration_tests/bundle_routes.test.ts b/src/core/server/core_app/integration_tests/bundle_routes.test.ts index fbe2e9285ba29b..7c50e09b124688 100644 --- a/src/core/server/core_app/integration_tests/bundle_routes.test.ts +++ b/src/core/server/core_app/integration_tests/bundle_routes.test.ts @@ -10,6 +10,7 @@ import { resolve } from 'path'; import { readFile } from 'fs/promises'; import supertest from 'supertest'; import { contextServiceMock } from '../../context/context_service.mock'; +import { executionContextServiceMock } from '../../execution_context/execution_context_service.mock'; import { loggingSystemMock } from '../../logging/logging_system.mock'; import { HttpService, IRouter } from '../../http'; import { createHttpServer } from '../../http/test_utils'; @@ -53,6 +54,7 @@ describe('bundle routes', () => { it('serves images inside from the bundle path', async () => { const { server: innerServer, createRouter } = await server.setup({ context: contextSetup, + executionContext: executionContextServiceMock.createInternalSetupContract(), }); registerFooPluginRoute(createRouter('')); @@ -70,6 +72,7 @@ describe('bundle routes', () => { it('serves uncompressed js files', async () => { const { server: innerServer, createRouter } = await server.setup({ context: contextSetup, + executionContext: executionContextServiceMock.createInternalSetupContract(), }); registerFooPluginRoute(createRouter('')); @@ -87,6 +90,7 @@ describe('bundle routes', () => { it('returns 404 for files outside of the bundlePath', async () => { const { server: innerServer, createRouter } = await server.setup({ context: contextSetup, + executionContext: executionContextServiceMock.createInternalSetupContract(), }); registerFooPluginRoute(createRouter('')); @@ -100,6 +104,7 @@ describe('bundle routes', () => { it('returns 404 for non-existing files', async () => { const { server: innerServer, createRouter } = await server.setup({ context: contextSetup, + executionContext: executionContextServiceMock.createInternalSetupContract(), }); registerFooPluginRoute(createRouter('')); @@ -113,6 +118,7 @@ describe('bundle routes', () => { it('returns gzip version if present', async () => { const { server: innerServer, createRouter } = await server.setup({ context: contextSetup, + executionContext: executionContextServiceMock.createInternalSetupContract(), }); registerFooPluginRoute(createRouter('')); @@ -137,6 +143,7 @@ describe('bundle routes', () => { it('uses max-age cache-control', async () => { const { server: innerServer, createRouter } = await server.setup({ context: contextSetup, + executionContext: executionContextServiceMock.createInternalSetupContract(), }); registerFooPluginRoute(createRouter(''), { isDist: true }); @@ -155,6 +162,7 @@ describe('bundle routes', () => { it('uses etag cache-control', async () => { const { server: innerServer, createRouter } = await server.setup({ context: contextSetup, + executionContext: executionContextServiceMock.createInternalSetupContract(), }); registerFooPluginRoute(createRouter(''), { isDist: false }); diff --git a/src/core/server/http/cookie_session_storage.test.ts b/src/core/server/http/cookie_session_storage.test.ts index c8021638664234..45ddf57806f186 100644 --- a/src/core/server/http/cookie_session_storage.test.ts +++ b/src/core/server/http/cookie_session_storage.test.ts @@ -18,6 +18,7 @@ import { KibanaRequest } from './router'; import { Env } from '../config'; import { contextServiceMock } from '../context/context_service.mock'; +import { executionContextServiceMock } from '../execution_context/execution_context_service.mock'; import { loggingSystemMock } from '../logging/logging_system.mock'; import { getEnvOptions, configServiceMock } from '../config/mocks'; import { httpServerMock } from './http_server.mocks'; @@ -34,6 +35,7 @@ const contextSetup = contextServiceMock.createSetupContract(); const setupDeps = { context: contextSetup, + executionContext: executionContextServiceMock.createInternalSetupContract(), }; configService.atPath.mockImplementation((path) => { diff --git a/src/core/server/http/http_service.test.ts b/src/core/server/http/http_service.test.ts index ebb9ad971b8484..d8a7b542754801 100644 --- a/src/core/server/http/http_service.test.ts +++ b/src/core/server/http/http_service.test.ts @@ -18,6 +18,7 @@ import { httpServerMock } from './http_server.mocks'; import { ConfigService, Env } from '../config'; import { loggingSystemMock } from '../logging/logging_system.mock'; import { contextServiceMock } from '../context/context_service.mock'; +import { executionContextServiceMock } from '../execution_context/execution_context_service.mock'; import { config as cspConfig } from '../csp'; import { config as externalUrlConfig } from '../external_url'; @@ -45,6 +46,7 @@ const contextSetup = contextServiceMock.createSetupContract(); const setupDeps = { context: contextSetup, + executionContext: executionContextServiceMock.createInternalSetupContract(), }; const fakeHapiServer = { start: noop, diff --git a/src/core/server/http/integration_tests/lifecycle.test.ts b/src/core/server/http/integration_tests/lifecycle.test.ts index 6897160951aabf..da8abe55b65922 100644 --- a/src/core/server/http/integration_tests/lifecycle.test.ts +++ b/src/core/server/http/integration_tests/lifecycle.test.ts @@ -14,6 +14,7 @@ import { ensureRawRequest } from '../router'; import { HttpService } from '../http_service'; import { contextServiceMock } from '../../context/context_service.mock'; +import { executionContextServiceMock } from '../../execution_context/execution_context_service.mock'; import { loggingSystemMock } from '../../logging/logging_system.mock'; import { createHttpServer } from '../test_utils'; @@ -25,6 +26,7 @@ const contextSetup = contextServiceMock.createSetupContract(); const setupDeps = { context: contextSetup, + executionContext: executionContextServiceMock.createInternalSetupContract(), }; beforeEach(() => { diff --git a/src/core/server/http/integration_tests/lifecycle_handlers.test.ts b/src/core/server/http/integration_tests/lifecycle_handlers.test.ts index cbd300fdc9c09b..9928cedc54b533 100644 --- a/src/core/server/http/integration_tests/lifecycle_handlers.test.ts +++ b/src/core/server/http/integration_tests/lifecycle_handlers.test.ts @@ -18,6 +18,7 @@ import { IRouter, RouteRegistrar } from '../router'; import { configServiceMock } from '../../config/mocks'; import { contextServiceMock } from '../../context/context_service.mock'; +import { executionContextServiceMock } from '../../execution_context/execution_context_service.mock'; // eslint-disable-next-line @typescript-eslint/no-var-requires const pkg = require('../../../../../package.json'); @@ -31,6 +32,7 @@ const xsrfDisabledTestPath = '/xsrf/test/route/disabled'; const kibanaName = 'my-kibana-name'; const setupDeps = { context: contextServiceMock.createSetupContract(), + executionContext: executionContextServiceMock.createInternalSetupContract(), }; describe('core lifecycle handlers', () => { diff --git a/src/core/server/http/integration_tests/request.test.ts b/src/core/server/http/integration_tests/request.test.ts index 7571184363d2ee..64eb9d047cd5b2 100644 --- a/src/core/server/http/integration_tests/request.test.ts +++ b/src/core/server/http/integration_tests/request.test.ts @@ -15,6 +15,7 @@ import supertest from 'supertest'; import { HttpService } from '../http_service'; import { contextServiceMock } from '../../context/context_service.mock'; +import { executionContextServiceMock } from '../../execution_context/execution_context_service.mock'; import { loggingSystemMock } from '../../logging/logging_system.mock'; import { createHttpServer } from '../test_utils'; import { schema } from '@kbn/config-schema'; @@ -26,6 +27,7 @@ const contextSetup = contextServiceMock.createSetupContract(); const setupDeps = { context: contextSetup, + executionContext: executionContextServiceMock.createInternalSetupContract(), }; beforeEach(() => { diff --git a/src/core/server/http/integration_tests/router.test.ts b/src/core/server/http/integration_tests/router.test.ts index 354ab1c65d5651..1b2b0b966d3a27 100644 --- a/src/core/server/http/integration_tests/router.test.ts +++ b/src/core/server/http/integration_tests/router.test.ts @@ -12,6 +12,7 @@ import supertest from 'supertest'; import { schema } from '@kbn/config-schema'; import { contextServiceMock } from '../../context/context_service.mock'; +import { executionContextServiceMock } from '../../execution_context/execution_context_service.mock'; import { loggingSystemMock } from '../../logging/logging_system.mock'; import { createHttpServer } from '../test_utils'; import { HttpService } from '../http_service'; @@ -24,6 +25,7 @@ const contextSetup = contextServiceMock.createSetupContract(); const setupDeps = { context: contextSetup, + executionContext: executionContextServiceMock.createInternalSetupContract(), }; beforeEach(() => { diff --git a/src/core/server/metrics/integration_tests/server_collector.test.ts b/src/core/server/metrics/integration_tests/server_collector.test.ts index 8d850db551cf33..19e3e6a6c68f98 100644 --- a/src/core/server/metrics/integration_tests/server_collector.test.ts +++ b/src/core/server/metrics/integration_tests/server_collector.test.ts @@ -13,6 +13,7 @@ import { Server as HapiServer } from '@hapi/hapi'; import { createHttpServer } from '../../http/test_utils'; import { HttpService, IRouter } from '../../http'; import { contextServiceMock } from '../../context/context_service.mock'; +import { executionContextServiceMock } from '../../execution_context/execution_context_service.mock'; import { ServerMetricsCollector } from '../collectors/server'; const requestWaitDelay = 25; @@ -29,7 +30,10 @@ describe('ServerMetricsCollector', () => { beforeEach(async () => { server = createHttpServer(); const contextSetup = contextServiceMock.createSetupContract(); - const httpSetup = await server.setup({ context: contextSetup }); + const httpSetup = await server.setup({ + context: contextSetup, + executionContext: executionContextServiceMock.createInternalSetupContract(), + }); hapiServer = httpSetup.server; router = httpSetup.createRouter('/'); collector = new ServerMetricsCollector(hapiServer); diff --git a/src/core/server/saved_objects/routes/integration_tests/get.test.ts b/src/core/server/saved_objects/routes/integration_tests/get.test.ts index 295f80712b765e..e247a913f97791 100644 --- a/src/core/server/saved_objects/routes/integration_tests/get.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/get.test.ts @@ -11,6 +11,7 @@ import { registerGetRoute } from '../get'; import { ContextService } from '../../../context'; import { savedObjectsClientMock } from '../../service/saved_objects_client.mock'; import { CoreUsageStatsClient } from '../../../core_usage_data'; +import { executionContextServiceMock } from '../../../execution_context/execution_context_service.mock'; import { coreUsageStatsClientMock } from '../../../core_usage_data/core_usage_stats_client.mock'; import { coreUsageDataServiceMock } from '../../../core_usage_data/core_usage_data_service.mock'; import { HttpService, InternalHttpServiceSetup } from '../../../http'; @@ -33,6 +34,7 @@ describe('GET /api/saved_objects/{type}/{id}', () => { const contextService = new ContextService(coreContext); httpSetup = await server.setup({ context: contextService.setup({ pluginDependencies: new Map() }), + executionContext: executionContextServiceMock.createInternalSetupContract(), }); handlerContext = coreMock.createRequestHandlerContext(); diff --git a/src/core/server/saved_objects/routes/integration_tests/resolve.test.ts b/src/core/server/saved_objects/routes/integration_tests/resolve.test.ts index 96d79edd39d3d0..294267a0e2ae79 100644 --- a/src/core/server/saved_objects/routes/integration_tests/resolve.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/resolve.test.ts @@ -13,6 +13,7 @@ import { savedObjectsClientMock } from '../../service/saved_objects_client.mock' import { CoreUsageStatsClient } from '../../../core_usage_data'; import { coreUsageStatsClientMock } from '../../../core_usage_data/core_usage_stats_client.mock'; import { coreUsageDataServiceMock } from '../../../core_usage_data/core_usage_data_service.mock'; +import { executionContextServiceMock } from '../../../execution_context/execution_context_service.mock'; import { HttpService, InternalHttpServiceSetup } from '../../../http'; import { createHttpServer, createCoreContext } from '../../../http/test_utils'; import { coreMock } from '../../../mocks'; @@ -33,6 +34,7 @@ describe('GET /api/saved_objects/resolve/{type}/{id}', () => { const contextService = new ContextService(coreContext); httpSetup = await server.setup({ context: contextService.setup({ pluginDependencies: new Map() }), + executionContext: executionContextServiceMock.createInternalSetupContract(), }); handlerContext = coreMock.createRequestHandlerContext(); diff --git a/src/core/server/status/routes/integration_tests/status.test.ts b/src/core/server/status/routes/integration_tests/status.test.ts index 841dc43cc4ee3b..03b9a1462e0c39 100644 --- a/src/core/server/status/routes/integration_tests/status.test.ts +++ b/src/core/server/status/routes/integration_tests/status.test.ts @@ -20,6 +20,7 @@ import { HttpService, InternalHttpServiceSetup } from '../../../http'; import { registerStatusRoute } from '../status'; import { ServiceStatus, ServiceStatusLevels } from '../../types'; import { statusServiceMock } from '../../status_service.mock'; +import { executionContextServiceMock } from '../../../execution_context/execution_context_service.mock'; const coreId = Symbol('core'); @@ -35,6 +36,7 @@ describe('GET /api/status', () => { server = createHttpServer(coreContext); httpSetup = await server.setup({ context: contextService.setup({ pluginDependencies: new Map() }), + executionContext: executionContextServiceMock.createInternalSetupContract(), }); metrics = metricsServiceMock.createSetupContract(); From 3a5347f51fc9f47534b3e80c242aab7ba0f87dac Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Mon, 28 Jun 2021 12:58:10 +0300 Subject: [PATCH 06/36] update settings docs --- docs/setup/settings.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index c3c29adcea18f9..6b7a1cac5b3580 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -579,7 +579,7 @@ identifies this {kib} instance. *Default: `"your-hostname"`* setting specifies the port to use. *Default: `5601`* |[[server-requestId-allowFromAnyIp]] `server.requestId.allowFromAnyIp:` - | Sets whether or not the X-Opaque-Id header should be trusted from any IP address for identifying requests in logs and forwarded to Elasticsearch. + | Sets whether or not the `X-Opaque-Id` header should be trusted from any IP address for identifying requests in logs and forwarded to Elasticsearch. | `server.requestId.ipAllowlist:` | A list of IPv4 and IPv6 address which the `X-Opaque-Id` header should be trusted from. Normally this would be set to the IP addresses of the load balancers or reverse-proxy that end users use to access Kibana. If any are set, <> must also be set to `false.` From 7277b5dec249c453d5397808824e1733951af4f0 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Mon, 28 Jun 2021 13:00:26 +0300 Subject: [PATCH 07/36] add execution context test plugin --- .../core_plugin_execution_context/kibana.json | 8 +++++ .../package.json | 14 +++++++++ .../server/index.ts | 11 +++++++ .../server/plugin.ts | 31 +++++++++++++++++++ .../tsconfig.json | 15 +++++++++ 5 files changed, 79 insertions(+) create mode 100644 test/plugin_functional/plugins/core_plugin_execution_context/kibana.json create mode 100644 test/plugin_functional/plugins/core_plugin_execution_context/package.json create mode 100644 test/plugin_functional/plugins/core_plugin_execution_context/server/index.ts create mode 100644 test/plugin_functional/plugins/core_plugin_execution_context/server/plugin.ts create mode 100644 test/plugin_functional/plugins/core_plugin_execution_context/tsconfig.json diff --git a/test/plugin_functional/plugins/core_plugin_execution_context/kibana.json b/test/plugin_functional/plugins/core_plugin_execution_context/kibana.json new file mode 100644 index 00000000000000..625745202e39bb --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_execution_context/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "corePluginExecutionContext", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["core_plugin_execution_context"], + "server": true, + "ui": false +} diff --git a/test/plugin_functional/plugins/core_plugin_execution_context/package.json b/test/plugin_functional/plugins/core_plugin_execution_context/package.json new file mode 100644 index 00000000000000..41f12a8a9e7852 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_execution_context/package.json @@ -0,0 +1,14 @@ +{ + "name": "core_plugin_a", + "version": "1.0.0", + "main": "target/test/plugin_functional/plugins/core_plugin_a", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "SSPL-1.0 OR Elastic License 2.0", + "scripts": { + "kbn": "node ../../../../scripts/kbn.js", + "build": "rm -rf './target' && ../../../../node_modules/.bin/tsc" + } +} \ No newline at end of file diff --git a/test/plugin_functional/plugins/core_plugin_execution_context/server/index.ts b/test/plugin_functional/plugins/core_plugin_execution_context/server/index.ts new file mode 100644 index 00000000000000..019e3020967525 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_execution_context/server/index.ts @@ -0,0 +1,11 @@ +/* + * 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. + */ + +import { CorePluginExecutionContext } from './plugin'; + +export const plugin = () => new CorePluginExecutionContext(); diff --git a/test/plugin_functional/plugins/core_plugin_execution_context/server/plugin.ts b/test/plugin_functional/plugins/core_plugin_execution_context/server/plugin.ts new file mode 100644 index 00000000000000..48889c6d4a4557 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_execution_context/server/plugin.ts @@ -0,0 +1,31 @@ +/* + * 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. + */ + +import type { Plugin, CoreSetup } from 'kibana/server'; + +export class CorePluginExecutionContext implements Plugin { + public setup(core: CoreSetup, deps: {}) { + const router = core.http.createRouter(); + router.get( + { + path: '/execution_context/pass', + validate: false, + options: { + authRequired: false, + }, + }, + async (context, request, response) => { + const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping(); + return response.ok({ body: headers || {} }); + } + ); + } + + public start() {} + public stop() {} +} diff --git a/test/plugin_functional/plugins/core_plugin_execution_context/tsconfig.json b/test/plugin_functional/plugins/core_plugin_execution_context/tsconfig.json new file mode 100644 index 00000000000000..21662b2b64a18a --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_execution_context/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "server/**/*.ts", + ], + "exclude": [], + "references": [ + { "path": "../../../../src/core/tsconfig.json" } + ] +} From b9b6f33e551dfbfd7f7e8b31d189508d9e1822ea Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Mon, 28 Jun 2021 13:01:25 +0300 Subject: [PATCH 08/36] add a client-side test --- .../core_plugins/execution_context.ts | 45 +++++++++++++++++++ .../test_suites/core_plugins/index.ts | 1 + 2 files changed, 46 insertions(+) create mode 100644 test/plugin_functional/test_suites/core_plugins/execution_context.ts diff --git a/test/plugin_functional/test_suites/core_plugins/execution_context.ts b/test/plugin_functional/test_suites/core_plugins/execution_context.ts new file mode 100644 index 00000000000000..8e4686b58fbed4 --- /dev/null +++ b/test/plugin_functional/test_suites/core_plugins/execution_context.ts @@ -0,0 +1,45 @@ +/* + * 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. + */ + +import expect from '@kbn/expect'; +import { PluginFunctionalProviderContext } from '../../services'; +import '../../../../test/plugin_functional/plugins/core_provider_plugin/types'; + +export default function ({ getService, getPageObjects }: PluginFunctionalProviderContext) { + describe('execution context', function () { + describe('passed for a client-side operation', () => { + const PageObjects = getPageObjects(['common']); + const browser = getService('browser'); + + before(async () => { + await PageObjects.common.navigateToApp('home'); + }); + + it('manually created context', async () => { + expect( + await browser.execute(async () => { + const coreStart = window._coreProvider.start.core; + + const context = coreStart.executionContext.create({ + type: 'execution_context_app', + name: 'Execution context app', + id: '42', + description: 'a request initiated by Execution context app', + }); + + const result = await coreStart.http.get('/execution_context/pass', { + context, + }); + + return result['x-opaque-id']; + }) + ).to.be('kibana:execution_context_app:42'); + }); + }); + }); +} diff --git a/test/plugin_functional/test_suites/core_plugins/index.ts b/test/plugin_functional/test_suites/core_plugins/index.ts index 87a153a24570d9..79850dd6333756 100644 --- a/test/plugin_functional/test_suites/core_plugins/index.ts +++ b/test/plugin_functional/test_suites/core_plugins/index.ts @@ -12,6 +12,7 @@ export default function ({ loadTestFile }: PluginFunctionalProviderContext) { describe('core plugins', () => { loadTestFile(require.resolve('./applications')); loadTestFile(require.resolve('./elasticsearch_client')); + loadTestFile(require.resolve('./execution_context')); loadTestFile(require.resolve('./server_plugins')); loadTestFile(require.resolve('./ui_plugins')); loadTestFile(require.resolve('./ui_settings')); From dcae7fb4a01b13c7a2aa605c1b7b51e9c65ab6aa Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Mon, 28 Jun 2021 13:03:29 +0300 Subject: [PATCH 09/36] remove requestId from execution context --- src/core/types/execution_context.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/types/execution_context.ts b/src/core/types/execution_context.ts index 62d9a99f4dfcb8..18066af336990c 100644 --- a/src/core/types/execution_context.ts +++ b/src/core/types/execution_context.ts @@ -8,7 +8,6 @@ /** @internal */ export interface KibanaExecutionContext { - readonly requestId: string; /** * Kibana application initated an operation. * Can be narrowed to an enum later. From 2ba10999061654247605dce8b950e35d794d2432 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Tue, 29 Jun 2021 16:02:09 +0300 Subject: [PATCH 10/36] add execution context service for the client side --- .../execution_context_container.ts | 37 +++++++++++++++++++ .../execution_context_service.mock.ts | 28 ++++++++++++++ .../execution_context_service.ts | 33 +++++++++++++++++ src/core/public/execution_context/index.ts | 11 ++++++ 4 files changed, 109 insertions(+) create mode 100644 src/core/public/execution_context/execution_context_container.ts create mode 100644 src/core/public/execution_context/execution_context_service.mock.ts create mode 100644 src/core/public/execution_context/execution_context_service.ts create mode 100644 src/core/public/execution_context/index.ts diff --git a/src/core/public/execution_context/execution_context_container.ts b/src/core/public/execution_context/execution_context_container.ts new file mode 100644 index 00000000000000..16995c02043825 --- /dev/null +++ b/src/core/public/execution_context/execution_context_container.ts @@ -0,0 +1,37 @@ +/* + * 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. + */ +import type { KibanaExecutionContext } from '../../types'; + +// Switch to the standard Baggage header +// https://github.com/elastic/apm-agent-rum-js/issues/1040 +export const BAGGAGE_HEADER = 'x-kbn-context'; + +// Maximum number of bytes per a single name-value pair allowed by w3c spec +// https://w3c.github.io/baggage/ +const BAGGAGE_MAX_PER_NAME_VALUE_PAIRS = 4096; + +// a single character can use up to 4 bytes +const MAX_BAGGAGE_LENGTH = BAGGAGE_MAX_PER_NAME_VALUE_PAIRS / 4; + +// the trimmed value in the server logs is better than nothing. +function enforceMaxLength(header: string): string { + return header.slice(0, MAX_BAGGAGE_LENGTH); +} + +export class ExecutionContextContainer { + readonly #context: Readonly; + constructor(context: Readonly) { + this.#context = context; + } + toString(): string { + return enforceMaxLength(JSON.stringify(this.#context)); + } + toHeader() { + return { [BAGGAGE_HEADER]: this.toString() }; + } +} diff --git a/src/core/public/execution_context/execution_context_service.mock.ts b/src/core/public/execution_context/execution_context_service.mock.ts new file mode 100644 index 00000000000000..8956653d285ac0 --- /dev/null +++ b/src/core/public/execution_context/execution_context_service.mock.ts @@ -0,0 +1,28 @@ +/* + * 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. + */ +import type { PublicMethodsOf } from '@kbn/utility-types'; +import type { ExecutionContextServiceStart } from './execution_context_service'; +import type { ExecutionContextContainer } from './execution_context_container'; + +const creteContainerMock = () => { + const mock: jest.Mocked> = { + toString: jest.fn(), + toHeader: jest.fn(), + }; + return mock; +}; +const createStartContractMock = () => { + const mock: jest.Mocked = { + create: jest.fn().mockReturnValue(creteContainerMock()), + }; + return mock; +}; + +export const executionContextServiceMock = { + createStartContract: createStartContractMock, +}; diff --git a/src/core/public/execution_context/execution_context_service.ts b/src/core/public/execution_context/execution_context_service.ts new file mode 100644 index 00000000000000..f7595e4a345eff --- /dev/null +++ b/src/core/public/execution_context/execution_context_service.ts @@ -0,0 +1,33 @@ +/* + * 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. + */ +import type { CoreService, KibanaExecutionContext } from '../../types'; +import { ExecutionContextContainer } from './execution_context_container'; + +export interface ExecutionContextServiceStart { + /** + * Creates a context container carrying the meta-data of a runtime operation. + * Provided meta-data will be propagated to Kibana and Elasticsearch servers. + * ```js + * const context = executionContext.create(...); + * http.fetch('/endpoint/', { context }); + * ``` + */ + create: (context: KibanaExecutionContext) => ExecutionContextContainer; +} + +export class ExecutionContextService implements CoreService { + setup() {} + start(): ExecutionContextServiceStart { + return { + create(context: KibanaExecutionContext) { + return new ExecutionContextContainer(context); + }, + }; + } + stop() {} +} diff --git a/src/core/public/execution_context/index.ts b/src/core/public/execution_context/index.ts new file mode 100644 index 00000000000000..efb501b61b5f03 --- /dev/null +++ b/src/core/public/execution_context/index.ts @@ -0,0 +1,11 @@ +/* + * 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 { ExecutionContextService } from './execution_context_service'; +export type { ExecutionContextServiceStart } from './execution_context_service'; +export type { ExecutionContextContainer } from './execution_context_container'; From c141db4f6a0fe8db05865f8d88690385e44ca89d Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Tue, 29 Jun 2021 16:02:44 +0300 Subject: [PATCH 11/36] expose execution context service to plugins --- src/core/public/core_system.ts | 6 ++++++ src/core/public/http/fetch.ts | 1 + src/core/public/http/types.ts | 3 +++ src/core/public/index.ts | 3 +++ src/core/public/plugins/plugin_context.ts | 1 + src/core/public/plugins/plugins_service.test.ts | 2 ++ 6 files changed, 16 insertions(+) diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index 9a28bf45df9273..0f4bcc98315517 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -29,6 +29,7 @@ import { SavedObjectsService } from './saved_objects'; import { IntegrationsService } from './integrations'; import { DeprecationsService } from './deprecations'; import { CoreApp } from './core_app'; +import { ExecutionContextService } from './execution_context'; import type { InternalApplicationSetup, InternalApplicationStart } from './application/types'; interface Params { @@ -83,6 +84,7 @@ export class CoreSystem { private readonly integrations: IntegrationsService; private readonly coreApp: CoreApp; private readonly deprecations: DeprecationsService; + private readonly executionContext: ExecutionContextService; private readonly rootDomElement: HTMLElement; private readonly coreContext: CoreContext; private fatalErrorsSetup: FatalErrorsSetup | null = null; @@ -118,6 +120,7 @@ export class CoreSystem { this.application = new ApplicationService(); this.integrations = new IntegrationsService(); this.deprecations = new DeprecationsService(); + this.executionContext = new ExecutionContextService(); this.plugins = new PluginsService(this.coreContext, injectedMetadata.uiPlugins); this.coreApp = new CoreApp(this.coreContext); @@ -137,6 +140,7 @@ export class CoreSystem { const http = this.http.setup({ injectedMetadata, fatalErrors: this.fatalErrorsSetup }); const uiSettings = this.uiSettings.setup({ http, injectedMetadata }); const notifications = this.notifications.setup({ uiSettings }); + this.executionContext.setup(); const application = this.application.setup({ http }); this.coreApp.setup({ application, http, injectedMetadata, notifications }); @@ -201,6 +205,7 @@ export class CoreSystem { notifications, }); const deprecations = this.deprecations.start({ http }); + const executionContext = this.executionContext.start(); this.coreApp.start({ application, http, notifications, uiSettings }); @@ -217,6 +222,7 @@ export class CoreSystem { uiSettings, fatalErrors, deprecations, + executionContext, }; await this.plugins.start(core); diff --git a/src/core/public/http/fetch.ts b/src/core/public/http/fetch.ts index 345fcecbda445c..153968518ea7c5 100644 --- a/src/core/public/http/fetch.ts +++ b/src/core/public/http/fetch.ts @@ -123,6 +123,7 @@ export class Fetch { 'Content-Type': 'application/json', ...options.headers, 'kbn-version': this.params.kibanaVersion, + ...options.context?.toHeader(), }), }; diff --git a/src/core/public/http/types.ts b/src/core/public/http/types.ts index 73418eafccd71e..e2a69b6832b360 100644 --- a/src/core/public/http/types.ts +++ b/src/core/public/http/types.ts @@ -8,6 +8,7 @@ import { Observable } from 'rxjs'; import { MaybePromise } from '@kbn/utility-types'; +import type { ExecutionContextContainer } from '../execution_context'; /** @public */ export interface HttpSetup { @@ -270,6 +271,8 @@ export interface HttpFetchOptions extends HttpRequestInit { * response information. When `false`, the return type will just be the parsed response body. Defaults to `false`. */ asResponse?: boolean; + + context?: ExecutionContextContainer; } /** diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 32737ff427ef3e..1e42d42bbb2ec9 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -65,6 +65,7 @@ import { ApplicationSetup, Capabilities, ApplicationStart } from './application' import { DocLinksStart } from './doc_links'; import { SavedObjectsStart } from './saved_objects'; import { DeprecationsServiceStart } from './deprecations'; +import type { ExecutionContextServiceStart } from './execution_context'; export type { PackageInfo, @@ -269,6 +270,8 @@ export interface CoreStart { fatalErrors: FatalErrorsStart; /** {@link DeprecationsServiceStart} */ deprecations: DeprecationsServiceStart; + /** {@link ExecutionContextServiceStart} */ + executionContext: ExecutionContextServiceStart; /** * exposed temporarily until https://github.com/elastic/kibana/issues/41990 done * use *only* to retrieve config values. There is no way to set injected values diff --git a/src/core/public/plugins/plugin_context.ts b/src/core/public/plugins/plugin_context.ts index 49c895aa80fc40..be3cff54aca8e9 100644 --- a/src/core/public/plugins/plugin_context.ts +++ b/src/core/public/plugins/plugin_context.ts @@ -140,5 +140,6 @@ export function createPluginStartContext< }, fatalErrors: deps.fatalErrors, deprecations: deps.deprecations, + executionContext: deps.executionContext, }; } diff --git a/src/core/public/plugins/plugins_service.test.ts b/src/core/public/plugins/plugins_service.test.ts index d7114f14e2f003..d62a4bcdd1e510 100644 --- a/src/core/public/plugins/plugins_service.test.ts +++ b/src/core/public/plugins/plugins_service.test.ts @@ -35,6 +35,7 @@ import { CoreSetup, CoreStart, PluginInitializerContext } from '..'; import { docLinksServiceMock } from '../doc_links/doc_links_service.mock'; import { savedObjectsServiceMock } from '../saved_objects/saved_objects_service.mock'; import { deprecationsServiceMock } from '../deprecations/deprecations_service.mock'; +import { executionContextServiceMock } from '../execution_context/execution_context_service.mock'; export let mockPluginInitializers: Map; @@ -103,6 +104,7 @@ describe('PluginsService', () => { savedObjects: savedObjectsServiceMock.createStartContract(), fatalErrors: fatalErrorsServiceMock.createStartContract(), deprecations: deprecationsServiceMock.createStartContract(), + executionContext: executionContextServiceMock.createStartContract(), }; mockStartContext = { ...mockStartDeps, From c73111960d9653f8d122741e5a99e4b46a8dbbed Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Tue, 29 Jun 2021 16:04:01 +0300 Subject: [PATCH 12/36] add execution context service for the server-side --- .../execution_context_client.ts | 37 ------ .../execution_context_container.ts | 27 ++++ .../execution_context_service.mock.ts | 36 ++++-- .../execution_context_service.ts | 120 +++++++++++++++--- src/core/server/execution_context/index.ts | 10 +- 5 files changed, 161 insertions(+), 69 deletions(-) delete mode 100644 src/core/server/execution_context/execution_context_client.ts create mode 100644 src/core/server/execution_context/execution_context_container.ts diff --git a/src/core/server/execution_context/execution_context_client.ts b/src/core/server/execution_context/execution_context_client.ts deleted file mode 100644 index a008fbd4735d2f..00000000000000 --- a/src/core/server/execution_context/execution_context_client.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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. - */ -import { AsyncLocalStorage } from 'async_hooks'; -import type { KibanaExecutionContext } from '../../types'; - -/** @internal */ -export interface KibanaServerExecutionContext { - readonly requestId: string; - readonly parentContext?: KibanaExecutionContext; -} - -export interface IExecutionContext { - startWith(context: KibanaServerExecutionContext): void; - stop(): void; - get(): KibanaServerExecutionContext | undefined; -} - -export class ExecutionContextClient implements IExecutionContext { - private readonly asyncLocalStorage = new AsyncLocalStorage(); - startWith(context: KibanaServerExecutionContext) { - // we have to use enterWith since Hapi lifecycle model is built on event emitters. - // therefore if we wrapped request handler in asyncLocalStorage.run(), we would lose context in other lifecycles. - this.asyncLocalStorage.enterWith(context); - } - stop() { - // @ts-expect-error "undefined" is not supported in type definitions, which is wrong - this.asyncLocalStorage.enterWith(undefined); - } - get(): KibanaServerExecutionContext | undefined { - return this.asyncLocalStorage.getStore(); - } -} diff --git a/src/core/server/execution_context/execution_context_container.ts b/src/core/server/execution_context/execution_context_container.ts new file mode 100644 index 00000000000000..0c6a73f4d54ca7 --- /dev/null +++ b/src/core/server/execution_context/execution_context_container.ts @@ -0,0 +1,27 @@ +/* + * 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. + */ +import type { KibanaServerExecutionContext } from './execution_context_service'; + +// Switch to the standard Baggage header. blocked by +// https://github.com/elastic/apm-agent-nodejs/issues/2102 +export const BAGGAGE_HEADER = 'x-kbn-context'; + +export class ExecutionContextContainer { + readonly #context: Readonly; + constructor(context: Readonly) { + this.#context = context; + } + toString(): string { + const ctx = this.#context; + const contextStringified = ctx.type && ctx.id ? `kibana:${ctx.type}:${ctx.id}` : ''; + return contextStringified ? `${ctx.requestId};${contextStringified}` : ctx.requestId; + } + toJSON(): Readonly { + return this.#context; + } +} diff --git a/src/core/server/execution_context/execution_context_service.mock.ts b/src/core/server/execution_context/execution_context_service.mock.ts index bb32d4b94ae551..657805df273caa 100644 --- a/src/core/server/execution_context/execution_context_service.mock.ts +++ b/src/core/server/execution_context/execution_context_service.mock.ts @@ -6,25 +6,37 @@ * Side Public License, v 1. */ -import type { ExecutionContextSetup } from './execution_context_service'; -import type { IExecutionContext } from './execution_context_client'; +import type { + IExecutionContext, + InternalExecutionContextSetup, + ExecutionContextSetup, +} from './execution_context_service'; -const createExecutionContextClientMock = () => { - const clientMock: jest.Mocked = { - startWith: jest.fn(), - stop: jest.fn(), +const createExecutionContextMock = () => { + const mock: jest.Mocked = { + set: jest.fn(), + reset: jest.fn(), get: jest.fn(), + getParentContextFrom: jest.fn(), }; - return clientMock; + return mock; }; +const createInternalSetupContractMock = () => { + const setupContract: jest.Mocked = createExecutionContextMock(); + return setupContract; +}; + const createSetupContractMock = () => { - const setupContract: jest.Mocked = { - client: createExecutionContextClientMock(), + const mock: jest.Mocked = { + set: jest.fn(), + get: jest.fn(), }; - return setupContract; + return mock; }; export const executionContextServiceMock = { - createClient: createExecutionContextClientMock, - createInternalSetupContract: createSetupContractMock, + createInternalSetupContract: createInternalSetupContractMock, + createInternalStartContract: createInternalSetupContractMock, + createSetupContract: createSetupContractMock, + createStartContract: createSetupContractMock, }; diff --git a/src/core/server/execution_context/execution_context_service.ts b/src/core/server/execution_context/execution_context_service.ts index c41416ab18a92c..93422d382cb5dc 100644 --- a/src/core/server/execution_context/execution_context_service.ts +++ b/src/core/server/execution_context/execution_context_service.ts @@ -5,37 +5,121 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import type { CoreService } from '../../types'; +import { AsyncLocalStorage } from 'async_hooks'; + +import type { CoreService, KibanaExecutionContext } from '../../types'; import type { CoreContext } from '../core_context'; import type { Logger } from '../logging'; -import type { IExecutionContext } from './execution_context_client'; -import { ExecutionContextClient } from './execution_context_client'; -// TODO rename to Internal +import { BAGGAGE_HEADER, ExecutionContextContainer } from './execution_context_container'; + +/** + * @public + */ +export interface KibanaServerExecutionContext extends Partial { + requestId: string; +} + +/** + * @internal + */ +export interface IExecutionContext { + getParentContextFrom(headers: Record): KibanaExecutionContext | undefined; + set(context: Partial): void; + reset(): void; + get(): ExecutionContextContainer | undefined; +} + +/** + * @internal + */ +export type InternalExecutionContextSetup = IExecutionContext; + +/** + * @internal + */ +export type InternalExecutionContextStart = IExecutionContext; + +/** + * @public + */ export interface ExecutionContextSetup { - client: IExecutionContext; + /** + * Stores the meta-data of a runtime operation. + * Data are carried over all async operations automatically. + * The sequential calls merge provided "context" object shallowly. + **/ + set(context: Partial): void; + /** + * Retrieves an opearation meta-data for the current async context. + **/ + get(): ExecutionContextContainer | undefined; } -// TODO remove if not usedConfig -export interface ExecutionContextStart { - client: IExecutionContext; +/** + * @public + */ +export type ExecutionContextStart = ExecutionContextSetup; + +function parseHeader(header?: string): KibanaExecutionContext | undefined { + if (!header) return undefined; + try { + return JSON.parse(header); + } catch (e) { + return undefined; + } } -export class ExecutionContextService implements CoreService { - private readonly client: ExecutionContextClient; +export class ExecutionContextService + implements CoreService { private readonly log: Logger; + private readonly asyncLocalStorage: AsyncLocalStorage; + constructor(coreContext: CoreContext) { this.log = coreContext.logger.get('execution_context'); - // TODO add a config option to disable the service - this.client = new ExecutionContextClient(); + this.asyncLocalStorage = new AsyncLocalStorage(); + } + + setup(): InternalExecutionContextSetup { + return { + getParentContextFrom: this.getParentContextFrom.bind(this), + set: this.set.bind(this), + reset: this.reset.bind(this), + get: this.get.bind(this), + }; } - setup(): ExecutionContextSetup { - this.log.debug('setup execution context service'); + + start(): InternalExecutionContextStart { return { - client: this.client, + getParentContextFrom: this.getParentContextFrom.bind(this), + set: this.set.bind(this), + reset: this.reset.bind(this), + get: this.get.bind(this), }; } - // is it used? - start(): void {} - stop(): void | Promise {} + stop() {} + + private getParentContextFrom(headers: Record) { + const header = headers[BAGGAGE_HEADER]; + return parseHeader(header); + } + + private set(context: KibanaServerExecutionContext) { + const prevValue = this.asyncLocalStorage.getStore(); + // merges context objects shallowly. repeats the deafult logic of apm.setCustomContext(ctx) + const contextContainer = new ExecutionContextContainer({ ...prevValue?.toJSON(), ...context }); + // we have to use enterWith since Hapi lifecycle model is built on event emitters. + // therefore if we wrapped request handler in asyncLocalStorage.run(), we would lose context in other lifecycles. + this.asyncLocalStorage.enterWith(contextContainer); + this.log.trace(`stored the execution context: ${contextContainer.toJSON()}`); + } + + private reset() { + // @ts-expect-error "undefined" is not supported in type definitions, which is wrong + this.asyncLocalStorage.enterWith(undefined); + } + + private get(): ExecutionContextContainer | undefined { + return this.asyncLocalStorage.getStore(); + } } diff --git a/src/core/server/execution_context/index.ts b/src/core/server/execution_context/index.ts index 9c0b15338f9f56..1f378e933817f0 100644 --- a/src/core/server/execution_context/index.ts +++ b/src/core/server/execution_context/index.ts @@ -7,5 +7,11 @@ */ export { ExecutionContextService } from './execution_context_service'; -export type { ExecutionContextSetup, ExecutionContextStart } from './execution_context_service'; -export type { IExecutionContext } from './execution_context_client'; +export type { + InternalExecutionContextSetup, + InternalExecutionContextStart, + ExecutionContextSetup, + ExecutionContextStart, + IExecutionContext, +} from './execution_context_service'; +export type { ExecutionContextContainer } from './execution_context_container'; From dd2abe3e3703f39f0a519576eab8962985a7d11d Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Tue, 29 Jun 2021 16:05:29 +0300 Subject: [PATCH 13/36] update http service --- src/core/server/http/http_server.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index 33bfcf471c88fb..9d98b64987fad8 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -22,7 +22,7 @@ import { Observable } from 'rxjs'; import { take } from 'rxjs/operators'; import { Logger, LoggerFactory } from '../logging'; import { HttpConfig } from './http_config'; -import type { ExecutionContextSetup } from '../execution_context'; +import type { InternalExecutionContextSetup } from '../execution_context'; import { adoptToHapiAuthFormat, AuthenticationHandler } from './lifecycle/auth'; import { adoptToHapiOnPreAuth, OnPreAuthHandler } from './lifecycle/on_pre_auth'; import { adoptToHapiOnPostAuthFormat, OnPostAuthHandler } from './lifecycle/on_post_auth'; @@ -136,7 +136,7 @@ export class HttpServer { public async setup( config: HttpConfig, - executionContext?: ExecutionContextSetup + executionContext?: InternalExecutionContextSetup ): Promise { const serverOptions = getServerOptions(config); const listenerOptions = getListenerOptions(config); @@ -333,11 +333,13 @@ export class HttpServer { private setupRequestStateAssignment( config: HttpConfig, - executionContext?: ExecutionContextSetup + executionContext?: InternalExecutionContextSetup ) { this.server!.ext('onRequest', (request, responseToolkit) => { const requestId = getRequestId(request, config.requestId); - executionContext?.client.startWith({ requestId }); + + const parentContext = executionContext?.getParentContextFrom(request.headers); + executionContext?.set({ ...parentContext, requestId }); request.app = { ...(request.app ?? {}), @@ -348,10 +350,10 @@ export class HttpServer { }); } - private setupContextExecutionCleanup(executionContext?: ExecutionContextSetup) { + private setupContextExecutionCleanup(executionContext?: InternalExecutionContextSetup) { if (!executionContext) return; this.server!.events.on('response', function () { - executionContext.client.stop(); + executionContext.reset(); }); } From ea81fe746d014350fe8e3ba2878f4a80dd90f49e Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Tue, 29 Jun 2021 16:05:47 +0300 Subject: [PATCH 14/36] update elasticsearch service --- .../elasticsearch/client/configure_client.ts | 35 ++++++++++++++++--- .../elasticsearch_service.test.ts | 2 ++ .../elasticsearch/elasticsearch_service.ts | 9 +++-- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/core/server/elasticsearch/client/configure_client.ts b/src/core/server/elasticsearch/client/configure_client.ts index ae1a2f67b74c87..d91a61b8d6fa46 100644 --- a/src/core/server/elasticsearch/client/configure_client.ts +++ b/src/core/server/elasticsearch/client/configure_client.ts @@ -8,18 +8,45 @@ import { Buffer } from 'buffer'; import { stringify } from 'querystring'; -import { ApiError, Client, RequestEvent, errors } from '@elastic/elasticsearch'; -import type { RequestBody } from '@elastic/elasticsearch/lib/Transport'; +import { ApiError, Client, RequestEvent, errors, Transport } from '@elastic/elasticsearch'; +import type { + RequestBody, + TransportRequestParams, + TransportRequestOptions, +} from '@elastic/elasticsearch/lib/Transport'; +import type { ExecutionContextContainer } from '../../execution_context'; import { Logger } from '../../logging'; import { parseClientOptions, ElasticsearchClientConfig } from './client_config'; +const noop = () => undefined; + export const configureClient = ( config: ElasticsearchClientConfig, - { logger, type, scoped = false }: { logger: Logger; type: string; scoped?: boolean } + { + logger, + type, + scoped = false, + getExecutionContext = noop, + }: { + logger: Logger; + type: string; + scoped?: boolean; + getExecutionContext?: () => ExecutionContextContainer | undefined; + } ): Client => { const clientOptions = parseClientOptions(config, scoped); + class KibanaTransport extends Transport { + request(params: TransportRequestParams, options?: TransportRequestOptions) { + const opts = options || {}; + const opaqueId = getExecutionContext()?.toString(); + if (opaqueId && !opts.opaqueId) { + opts.opaqueId = opaqueId; + } + return super.request(params, opts); + } + } - const client = new Client(clientOptions); + const client = new Client({ ...clientOptions, Transport: KibanaTransport }); addLogging(client, logger.get('query', type)); return client; diff --git a/src/core/server/elasticsearch/elasticsearch_service.test.ts b/src/core/server/elasticsearch/elasticsearch_service.test.ts index 8a6da8d251e377..facb44eeab5ecd 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.test.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.test.ts @@ -15,6 +15,7 @@ import { configServiceMock, getEnvOptions } from '../config/mocks'; import { CoreContext } from '../core_context'; import { loggingSystemMock } from '../logging/logging_system.mock'; import { httpServiceMock } from '../http/http_service.mock'; +import { executionContextServiceMock } from '../execution_context/execution_context_service.mock'; import { ElasticsearchConfig } from './elasticsearch_config'; import { ElasticsearchService } from './elasticsearch_service'; import { elasticsearchServiceMock } from './elasticsearch_service.mock'; @@ -28,6 +29,7 @@ let elasticsearchService: ElasticsearchService; const configService = configServiceMock.create(); const setupDeps = { http: httpServiceMock.createInternalSetupContract(), + executionContext: executionContextServiceMock.createInternalSetupContract(), }; configService.atPath.mockReturnValue( new BehaviorSubject({ diff --git a/src/core/server/elasticsearch/elasticsearch_service.ts b/src/core/server/elasticsearch/elasticsearch_service.ts index 7da83145ccd425..deb2d49f708176 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.ts @@ -20,13 +20,15 @@ import { } from './legacy'; import { ClusterClient, ICustomClusterClient, ElasticsearchClientConfig } from './client'; import { ElasticsearchConfig, ElasticsearchConfigType } from './elasticsearch_config'; -import { InternalHttpServiceSetup, GetAuthHeaders } from '../http/'; +import type { InternalHttpServiceSetup, GetAuthHeaders } from '../http/'; +import type { InternalExecutionContextSetup, IExecutionContext } from '../execution_context'; import { InternalElasticsearchServiceSetup, InternalElasticsearchServiceStart } from './types'; import { pollEsNodesVersion } from './version_check/ensure_es_version'; import { calculateStatus$ } from './status'; interface SetupDeps { http: InternalHttpServiceSetup; + executionContext: InternalExecutionContextSetup; } /** @internal */ @@ -37,6 +39,7 @@ export class ElasticsearchService private stop$ = new Subject(); private kibanaVersion: string; private getAuthHeaders?: GetAuthHeaders; + private executionContextClient?: IExecutionContext; private createLegacyCustomClient?: ( type: string, @@ -60,6 +63,7 @@ export class ElasticsearchService const config = await this.config$.pipe(first()).toPromise(); this.getAuthHeaders = deps.http.getAuthHeaders; + this.executionContextClient = deps.executionContext; this.legacyClient = this.createLegacyClusterClient('data', config); this.client = this.createClusterClient('data', config); @@ -128,7 +132,8 @@ export class ElasticsearchService config, this.coreContext.logger.get('elasticsearch'), type, - this.getAuthHeaders + this.getAuthHeaders, + () => this.executionContextClient?.get() ); } From 92669ce70987fd5b6cd19a91fed7750b855ca461 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Tue, 29 Jun 2021 16:06:39 +0300 Subject: [PATCH 15/36] move integration tests from http to execution_context service --- .../integration_tests/tracing.test.ts | 401 ++++++++++++++++++ src/core/server/http/http_service.ts | 4 +- .../integration_tests/core_services.test.ts | 102 ----- 3 files changed, 403 insertions(+), 104 deletions(-) create mode 100644 src/core/server/execution_context/integration_tests/tracing.test.ts diff --git a/src/core/server/execution_context/integration_tests/tracing.test.ts b/src/core/server/execution_context/integration_tests/tracing.test.ts new file mode 100644 index 00000000000000..9572c61515aa3d --- /dev/null +++ b/src/core/server/execution_context/integration_tests/tracing.test.ts @@ -0,0 +1,401 @@ +/* + * 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. + */ + +import { + BAGGAGE_HEADER, + ExecutionContextContainer, +} from '../../../public/execution_context/execution_context_container'; +import * as kbnTestServer from '../../../test_helpers/kbn_server'; + +const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +const parentContext = { + type: 'test-type', + name: 'test-name', + id: '42', + description: 'test-description', +}; + +describe('trace', () => { + let esServer: kbnTestServer.TestElasticsearchUtils; + let root: ReturnType; + beforeAll(async () => { + const { startES } = kbnTestServer.createTestServers({ + adjustTimeout: jest.setTimeout, + }); + esServer = await startES(); + }); + + afterAll(async () => { + await esServer.stop(); + }); + + beforeEach(async () => { + root = kbnTestServer.createRootWithCorePlugins({ + plugins: { initialize: false }, + server: { + requestId: { + allowFromAnyIp: true, + }, + }, + }); + }, 30000); + + afterEach(async () => { + await root.shutdown(); + }); + + describe('x-opaque-id', () => { + it('passed to Elasticsearch unscoped client calls', async () => { + const { http } = await root.setup(); + const { createRouter } = http; + + const router = createRouter(''); + router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { + const { headers } = await context.core.elasticsearch.client.asInternalUser.ping(); + return res.ok({ body: headers || {} }); + }); + + await root.start(); + + const myOpaqueId = 'my-opaque-id'; + const response = await kbnTestServer.request + .get(root, '/execution-context') + .set('x-opaque-id', myOpaqueId) + .expect(200); + + const header = response.body['x-opaque-id']; + expect(header).toBe(myOpaqueId); + }); + + it('passed to Elasticsearch scoped client calls', async () => { + const { http } = await root.setup(); + const { createRouter } = http; + + const router = createRouter(''); + router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { + const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping(); + return res.ok({ body: headers || {} }); + }); + + await root.start(); + + const myOpaqueId = 'my-opaque-id'; + const response = await kbnTestServer.request + .get(root, '/execution-context') + .set('x-opaque-id', myOpaqueId) + .expect(200); + + const header = response.body['x-opaque-id']; + expect(header).toBe(myOpaqueId); + }); + + it('generated and attached to Elasticsearch unscoped client calls if not specifed', async () => { + const { http } = await root.setup(); + const { createRouter } = http; + + const router = createRouter(''); + router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { + const { headers } = await context.core.elasticsearch.client.asInternalUser.ping(); + return res.ok({ body: headers || {} }); + }); + + await root.start(); + + const response = await kbnTestServer.request.get(root, '/execution-context').expect(200); + + const header = response.body['x-opaque-id']; + expect(header).toEqual(expect.any(String)); + }); + + it('generated and attached to Elasticsearch scoped client calls if not specifed', async () => { + const { http } = await root.setup(); + const { createRouter } = http; + + const router = createRouter(''); + router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { + const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping(); + return res.ok({ body: headers || {} }); + }); + + await root.start(); + + const response = await kbnTestServer.request.get(root, '/execution-context').expect(200); + + const header = response.body['x-opaque-id']; + expect(header).toEqual(expect.any(String)); + }); + + it('can be rewritten during Elasticsearch client call', async () => { + const { http } = await root.setup(); + const { createRouter } = http; + + const router = createRouter(''); + router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { + const { headers } = await context.core.elasticsearch.client.asInternalUser.ping( + {}, + { + opaqueId: 'new-opaque-id', + } + ); + return res.ok({ body: headers || {} }); + }); + + await root.start(); + + const myOpaqueId = 'my-opaque-id'; + const response = await kbnTestServer.request + .get(root, '/execution-context') + .set('x-opaque-id', myOpaqueId) + .expect(200); + + const header = response.body['x-opaque-id']; + expect(header).toBe('new-opaque-id'); + }); + }); + describe('execution context', () => { + it('sets execution context for a request handler', async () => { + const { executionContext, http } = await root.setup(); + const { createRouter } = http; + + const router = createRouter(''); + router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { + executionContext.set(parentContext); + await delay(100); + return res.ok({ body: executionContext.get() }); + }); + + await root.start(); + const response = await kbnTestServer.request.get(root, '/execution-context').expect(200); + expect(response.body).toEqual({ ...parentContext, requestId: expect.any(String) }); + }); + + it('execution context is uniq for sequential requests', async () => { + const { executionContext, http } = await root.setup(); + const { createRouter } = http; + + const router = createRouter(''); + router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { + executionContext.set(parentContext); + await delay(100); + return res.ok({ body: executionContext.get() }); + }); + + await root.start(); + const responseA = await kbnTestServer.request.get(root, '/execution-context').expect(200); + const responseB = await kbnTestServer.request.get(root, '/execution-context').expect(200); + + expect(responseA.body).toEqual({ ...parentContext, requestId: expect.any(String) }); + expect(responseB.body).toEqual({ ...parentContext, requestId: expect.any(String) }); + expect(responseA.body.requestId).not.toBe(responseB.body.requestId); + }); + + it('execution context is uniq for concurrent requests', async () => { + const { executionContext, http } = await root.setup(); + const { createRouter } = http; + + const router = createRouter(''); + let id = 2; + router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { + executionContext.set(parentContext); + await delay(id-- * 100); + return res.ok({ body: executionContext.get() }); + }); + + await root.start(); + const responseA = kbnTestServer.request.get(root, '/execution-context'); + const responseB = kbnTestServer.request.get(root, '/execution-context'); + const responseC = kbnTestServer.request.get(root, '/execution-context'); + + const [{ body: bodyA }, { body: bodyB }, { body: bodyC }] = await Promise.all([ + responseA, + responseB, + responseC, + ]); + expect(bodyA.requestId).toBeDefined(); + expect(bodyB.requestId).toBeDefined(); + expect(bodyC.requestId).toBeDefined(); + + expect(bodyA.requestId).not.toBe(bodyB.requestId); + expect(bodyB.requestId).not.toBe(bodyC.requestId); + expect(bodyA.requestId).not.toBe(bodyC.requestId); + }); + + it('parses the parent context if present', async () => { + const { executionContext, http } = await root.setup(); + const { createRouter } = http; + + const router = createRouter(''); + router.get({ path: '/execution-context', validate: false }, (context, req, res) => + res.ok({ body: executionContext.get() }) + ); + + await root.start(); + const response = await kbnTestServer.request + .get(root, '/execution-context') + .set(BAGGAGE_HEADER, new ExecutionContextContainer(parentContext).toString()) + .expect(200); + + expect(response.body).toEqual({ ...parentContext, requestId: expect.any(String) }); + }); + + it('execution context is the same for all the lifecycle events', async () => { + const { executionContext, http } = await root.setup(); + const { + createRouter, + registerOnPreRouting, + registerOnPreAuth, + registerAuth, + registerOnPostAuth, + registerOnPreResponse, + } = http; + + const router = createRouter(''); + router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { + return res.ok({ body: executionContext.get()?.toJSON() }); + }); + + let onPreRoutingContext; + registerOnPreRouting((request, response, t) => { + onPreRoutingContext = executionContext.get()?.toJSON(); + return t.next(); + }); + + let onPreAuthContext; + registerOnPreAuth((request, response, t) => { + onPreAuthContext = executionContext.get()?.toJSON(); + return t.next(); + }); + + let authContext; + registerAuth((request, response, t) => { + authContext = executionContext.get()?.toJSON(); + return t.authenticated(); + }); + + let onPostAuthContext; + registerOnPostAuth((request, response, t) => { + onPostAuthContext = executionContext.get()?.toJSON(); + return t.next(); + }); + + let onPreResponseContext; + registerOnPreResponse((request, response, t) => { + onPreResponseContext = executionContext.get()?.toJSON(); + return t.next(); + }); + + await root.start(); + const response = await kbnTestServer.request + .get(root, '/execution-context') + .set(BAGGAGE_HEADER, new ExecutionContextContainer(parentContext).toString()) + .expect(200); + + expect(response.body).toEqual({ ...parentContext, requestId: expect.any(String) }); + + expect(response.body).toEqual(onPreRoutingContext); + expect(response.body).toEqual(onPreAuthContext); + expect(response.body).toEqual(authContext); + expect(response.body).toEqual(onPostAuthContext); + expect(response.body).toEqual(onPreResponseContext); + }); + + it('propagates context to Elasticsearch scoped client', async () => { + const { http } = await root.setup(); + const { createRouter } = http; + + const router = createRouter(''); + router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { + const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping(); + return res.ok({ body: headers || {} }); + }); + + await root.start(); + + const response = await kbnTestServer.request + .get(root, '/execution-context') + .set(BAGGAGE_HEADER, new ExecutionContextContainer(parentContext).toString()) + .expect(200); + + const header = response.body['x-opaque-id']; + expect(header).toContain('kibana:test-type:42'); + }); + + it('propagates context to Elasticsearch unscoped client', async () => { + const { http } = await root.setup(); + const { createRouter } = http; + + const router = createRouter(''); + router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { + const { headers } = await context.core.elasticsearch.client.asInternalUser.ping(); + return res.ok({ body: headers || {} }); + }); + + await root.start(); + + const response = await kbnTestServer.request + .get(root, '/execution-context') + .set(BAGGAGE_HEADER, new ExecutionContextContainer(parentContext).toString()) + .expect(200); + + const header = response.body['x-opaque-id']; + expect(header).toContain('kibana:test-type:42'); + }); + + it('a repeat call overwrites the old context', async () => { + const { http, executionContext } = await root.setup(); + const { createRouter } = http; + + const router = createRouter(''); + const newContext = { + type: 'new-type', + name: 'new-name', + id: '41', + description: 'new-description', + }; + router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { + executionContext.set(newContext); + const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping(); + return res.ok({ body: headers || {} }); + }); + + await root.start(); + + const response = await kbnTestServer.request + .get(root, '/execution-context') + .set(BAGGAGE_HEADER, new ExecutionContextContainer(parentContext).toString()) + .expect(200); + + const header = response.body['x-opaque-id']; + expect(header).toContain('kibana:new-type:41'); + }); + + it('does not affect "x-opaque-id" set by user', async () => { + const { http, executionContext } = await root.setup(); + const { createRouter } = http; + + const router = createRouter(''); + router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { + executionContext.set(parentContext); + const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping(); + return res.ok({ body: headers || {} }); + }); + + await root.start(); + + const myOpaqueId = 'my-opaque-id'; + const response = await kbnTestServer.request + .get(root, '/execution-context') + .set('x-opaque-id', myOpaqueId) + .expect(200); + + const header = response.body['x-opaque-id']; + expect(header).toBe('my-opaque-id;kibana:test-type:42'); + }); + }); +}); diff --git a/src/core/server/http/http_service.ts b/src/core/server/http/http_service.ts index 4378e70f0f7dc0..0097aab82b21c2 100644 --- a/src/core/server/http/http_service.ts +++ b/src/core/server/http/http_service.ts @@ -11,7 +11,7 @@ import { first, map } from 'rxjs/operators'; import { pick } from '@kbn/std'; import type { RequestHandlerContext } from 'src/core/server'; -import type { ExecutionContextSetup } from '../execution_context'; +import type { InternalExecutionContextSetup } from '../execution_context'; import { CoreService } from '../../types'; import { Logger, LoggerFactory } from '../logging'; import { ContextSetup } from '../context'; @@ -42,7 +42,7 @@ import { interface SetupDeps { context: ContextSetup; - executionContext: ExecutionContextSetup; + executionContext: InternalExecutionContextSetup; } /** @internal */ diff --git a/src/core/server/http/integration_tests/core_services.test.ts b/src/core/server/http/integration_tests/core_services.test.ts index af0b24f663c90a..99b63fc73687a1 100644 --- a/src/core/server/http/integration_tests/core_services.test.ts +++ b/src/core/server/http/integration_tests/core_services.test.ts @@ -408,106 +408,4 @@ describe('http service', () => { expect(body.message).toMatch('[error_type]: error_reason'); }); }); - - describe('execution context', () => { - let root: ReturnType; - beforeEach(async () => { - root = kbnTestServer.createRoot({ plugins: { initialize: false } }); - }, 30000); - - afterEach(async () => { - await root.shutdown(); - }); - - it('sets execution context for a request handler', async () => { - const { executionContext, http } = await root.setup(); - const { createRouter } = http; - - const router = createRouter(''); - router.get({ path: '/execution-context', validate: false }, (context, req, res) => - res.ok({ body: executionContext.client.get() }) - ); - - await root.start(); - const response = await kbnTestServer.request.get(root, '/execution-context').expect(200); - expect(response.body).toEqual({ requestId: expect.any(String) }); - expect(response.body.requestId).not.toHaveLength(0); - }); - - it('execution context is uniq for every request', async () => { - const { executionContext, http } = await root.setup(); - const { createRouter } = http; - - const router = createRouter(''); - router.get({ path: '/execution-context', validate: false }, (context, req, res) => - res.ok({ body: executionContext.client.get() }) - ); - - await root.start(); - const responseA = await kbnTestServer.request.get(root, '/execution-context').expect(200); - const responseB = await kbnTestServer.request.get(root, '/execution-context').expect(200); - - expect(responseA.body).toEqual({ requestId: expect.any(String) }); - expect(responseB.body).toEqual({ requestId: expect.any(String) }); - expect(responseA.body.requestId).not.toBe(responseB.body.requestId); - }); - - it('execution context is the same for all the lifecycle events', async () => { - const { executionContext, http } = await root.setup(); - const { - createRouter, - registerOnPreRouting, - registerOnPreAuth, - registerAuth, - registerOnPostAuth, - registerOnPreResponse, - } = http; - - const router = createRouter(''); - router.get({ path: '/execution-context', validate: false }, (context, req, res) => - res.ok({ body: executionContext.client.get() }) - ); - - let onPreRoutingContext; - registerOnPreRouting((request, response, t) => { - onPreRoutingContext = executionContext.client.get(); - return t.next(); - }); - - let onPreAuthContext; - registerOnPreAuth((request, response, t) => { - onPreAuthContext = executionContext.client.get(); - return t.next(); - }); - - let authContext; - registerAuth((request, response, t) => { - authContext = executionContext.client.get(); - return t.authenticated(); - }); - - let onPostAuthContext; - registerOnPostAuth((request, response, t) => { - onPostAuthContext = executionContext.client.get(); - return t.next(); - }); - - let onPreResponseContext; - registerOnPreResponse((request, response, t) => { - onPreResponseContext = executionContext.client.get(); - return t.next(); - }); - - await root.start(); - const response = await kbnTestServer.request.get(root, '/execution-context').expect(200); - - expect(response.body).toEqual({ requestId: expect.any(String) }); - - expect(response.body).toEqual(onPreRoutingContext); - expect(response.body).toEqual(onPreAuthContext); - expect(response.body).toEqual(authContext); - expect(response.body).toEqual(onPostAuthContext); - expect(response.body).toEqual(onPreResponseContext); - }); - }); }); From 23bbb0ad6b8c5c0e239e73115d52baee88bbe446 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Tue, 29 Jun 2021 16:07:06 +0300 Subject: [PATCH 16/36] integrate in es client --- .../elasticsearch/client/cluster_client.ts | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/core/server/elasticsearch/client/cluster_client.ts b/src/core/server/elasticsearch/client/cluster_client.ts index 278bc6f3b0a65c..dcbd1d24a7a972 100644 --- a/src/core/server/elasticsearch/client/cluster_client.ts +++ b/src/core/server/elasticsearch/client/cluster_client.ts @@ -8,8 +8,9 @@ import { Client } from '@elastic/elasticsearch'; import { Logger } from '../../logging'; -import { GetAuthHeaders, Headers, isKibanaRequest, isRealRequest } from '../../http'; +import { GetAuthHeaders, Headers, isRealRequest } from '../../http'; import { ensureRawRequest, filterHeaders } from '../../http/router'; +import type { ExecutionContextContainer } from '../../execution_context'; import { ScopeableRequest } from '../types'; import { ElasticsearchClient } from './types'; import { configureClient } from './configure_client'; @@ -54,6 +55,7 @@ export interface ICustomClusterClient extends IClusterClient { export class ClusterClient implements ICustomClusterClient { public readonly asInternalUser: Client; private readonly rootScopedClient: Client; + private readonly allowListHeaders: string[]; private isClosed = false; @@ -61,10 +63,17 @@ export class ClusterClient implements ICustomClusterClient { private readonly config: ElasticsearchClientConfig, logger: Logger, type: string, - private readonly getAuthHeaders: GetAuthHeaders = noop + private readonly getAuthHeaders: GetAuthHeaders = noop, + getExecutionContext: () => ExecutionContextContainer | undefined = noop ) { - this.asInternalUser = configureClient(config, { logger, type }); - this.rootScopedClient = configureClient(config, { logger, type, scoped: true }); + this.asInternalUser = configureClient(config, { logger, type, getExecutionContext }); + this.rootScopedClient = configureClient(config, { + logger, + type, + getExecutionContext, + scoped: true, + }); + this.allowListHeaders = this.config.requestHeadersWhitelist; } asScoped(request: ScopeableRequest) { @@ -87,13 +96,9 @@ export class ClusterClient implements ICustomClusterClient { let scopedHeaders: Headers; if (isRealRequest(request)) { const requestHeaders = ensureRawRequest(request).headers; - const requestIdHeaders = isKibanaRequest(request) ? { 'x-opaque-id': request.id } : {}; const authHeaders = this.getAuthHeaders(request); - scopedHeaders = filterHeaders({ ...requestHeaders, ...requestIdHeaders, ...authHeaders }, [ - 'x-opaque-id', - ...this.config.requestHeadersWhitelist, - ]); + scopedHeaders = filterHeaders({ ...requestHeaders, ...authHeaders }, this.allowListHeaders); } else { scopedHeaders = filterHeaders(request?.headers ?? {}, this.config.requestHeadersWhitelist); } From edbb8ee7046abf216c295035e8221df6becec5e4 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Tue, 29 Jun 2021 16:07:24 +0300 Subject: [PATCH 17/36] expose to plugins --- src/core/server/index.ts | 6 ++++++ src/core/server/internal_types.ts | 8 ++++++-- src/core/server/mocks.ts | 3 +++ src/core/server/plugins/plugin_context.ts | 2 ++ src/core/server/server.ts | 4 +++- 5 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 77946e15ef6863..47af421882b1a9 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -69,6 +69,8 @@ import { CoreServicesUsageData, } from './core_usage_data'; +import type { ExecutionContextSetup, ExecutionContextStart } from './execution_context'; + export type { CoreUsageStats, CoreUsageData, @@ -475,6 +477,8 @@ export interface CoreSetup, object, any]>, []>() .mockResolvedValue([createCoreStartMock(), pluginStartDeps, pluginStartContract]), @@ -162,6 +163,7 @@ function createCoreStartMock() { savedObjects: savedObjectsServiceMock.createStartContract(), uiSettings: uiSettingsServiceMock.createStartContract(), coreUsageData: coreUsageDataServiceMock.createStartContract(), + executionContext: executionContextServiceMock.createInternalStartContract(), }; return mock; @@ -197,6 +199,7 @@ function createInternalCoreStartMock() { savedObjects: savedObjectsServiceMock.createInternalStartContract(), uiSettings: uiSettingsServiceMock.createStartContract(), coreUsageData: coreUsageDataServiceMock.createStartContract(), + executionContext: executionContextServiceMock.createInternalStartContract(), }; return startDeps; } diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index c466eb2b9ee09d..70fd1c60efa614 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -115,6 +115,7 @@ export function createPluginSetupContext( elasticsearch: { legacy: deps.elasticsearch.legacy, }, + executionContext: deps.executionContext, http: { createCookieSessionStorageFactory: deps.http.createCookieSessionStorageFactory, registerRouteHandlerContext: < @@ -195,6 +196,7 @@ export function createPluginStartContext( createClient: deps.elasticsearch.createClient, legacy: deps.elasticsearch.legacy, }, + executionContext: deps.executionContext, http: { auth: deps.http.auth, basePath: deps.http.basePath, diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 5408d5621d43cf..a665d99bab568c 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -150,6 +150,7 @@ export class Server { const elasticsearchServiceSetup = await this.elasticsearch.setup({ http: httpSetup, + executionContext: executionContextSetup, }); const metricsSetup = await this.metrics.setup({ http: httpSetup }); @@ -236,7 +237,7 @@ export class Server { this.log.debug('starting server'); const startTransaction = apm.startTransaction('server_start', 'kibana_platform'); - this.executionContext.start(); + const executionContextStart = this.executionContext.start(); const elasticsearchStart = await this.elasticsearch.start(); const soStartSpan = startTransaction?.startSpan('saved_objects.migration', 'migration'); const savedObjectsStart = await this.savedObjects.start({ @@ -260,6 +261,7 @@ export class Server { this.coreStart = { capabilities: capabilitiesStart, elasticsearch: elasticsearchStart, + executionContext: executionContextStart, http: httpStart, metrics: metricsStart, savedObjects: savedObjectsStart, From b9aeb331087939e1cec0bb2b76cc195bc616b885 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Tue, 29 Jun 2021 16:07:51 +0300 Subject: [PATCH 18/36] refactor functional tests --- .../test_suites/core_plugins/execution_context.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/plugin_functional/test_suites/core_plugins/execution_context.ts b/test/plugin_functional/test_suites/core_plugins/execution_context.ts index 8e4686b58fbed4..1a48a51ca94f68 100644 --- a/test/plugin_functional/test_suites/core_plugins/execution_context.ts +++ b/test/plugin_functional/test_suites/core_plugins/execution_context.ts @@ -38,7 +38,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide return result['x-opaque-id']; }) - ).to.be('kibana:execution_context_app:42'); + ).to.contain('kibana:execution_context_app:42'); }); }); }); From 9cbe43d932f26cb1f504ef1041708640ec1bede7 Mon Sep 17 00:00:00 2001 From: restrry Date: Tue, 29 Jun 2021 16:16:43 +0300 Subject: [PATCH 19/36] remove x-opaque-id from create_cluster tests --- .../client/cluster_client.test.ts | 56 +------------------ 1 file changed, 3 insertions(+), 53 deletions(-) diff --git a/src/core/server/elasticsearch/client/cluster_client.test.ts b/src/core/server/elasticsearch/client/cluster_client.test.ts index befad222030f9a..062adc3540c37b 100644 --- a/src/core/server/elasticsearch/client/cluster_client.test.ts +++ b/src/core/server/elasticsearch/client/cluster_client.test.ts @@ -135,7 +135,7 @@ describe('ClusterClient', () => { expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith({ - headers: { ...DEFAULT_HEADERS, foo: 'bar', 'x-opaque-id': expect.any(String) }, + headers: { ...DEFAULT_HEADERS, foo: 'bar' }, }); }); @@ -155,7 +155,7 @@ describe('ClusterClient', () => { expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith({ - headers: { ...DEFAULT_HEADERS, authorization: 'auth', 'x-opaque-id': expect.any(String) }, + headers: { ...DEFAULT_HEADERS, authorization: 'auth' }, }); }); @@ -179,7 +179,7 @@ describe('ClusterClient', () => { expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith({ - headers: { ...DEFAULT_HEADERS, authorization: 'auth', 'x-opaque-id': expect.any(String) }, + headers: { ...DEFAULT_HEADERS, authorization: 'auth' }, }); }); @@ -204,27 +204,6 @@ describe('ClusterClient', () => { ...DEFAULT_HEADERS, foo: 'bar', hello: 'dolly', - 'x-opaque-id': expect.any(String), - }, - }); - }); - - it('adds the x-opaque-id header based on the request id', () => { - const config = createConfig(); - getAuthHeaders.mockReturnValue({}); - - const clusterClient = new ClusterClient(config, logger, 'custom-type', getAuthHeaders); - const request = httpServerMock.createKibanaRequest({ - kibanaRequestState: { requestId: 'my-fake-id', requestUuid: 'ignore-this-id' }, - }); - - clusterClient.asScoped(request); - - expect(scopedClient.child).toHaveBeenCalledTimes(1); - expect(scopedClient.child).toHaveBeenCalledWith({ - headers: { - ...DEFAULT_HEADERS, - 'x-opaque-id': 'my-fake-id', }, }); }); @@ -252,7 +231,6 @@ describe('ClusterClient', () => { ...DEFAULT_HEADERS, foo: 'auth', hello: 'dolly', - 'x-opaque-id': expect.any(String), }, }); }); @@ -280,7 +258,6 @@ describe('ClusterClient', () => { ...DEFAULT_HEADERS, foo: 'request', hello: 'dolly', - 'x-opaque-id': expect.any(String), }, }); }); @@ -303,7 +280,6 @@ describe('ClusterClient', () => { expect(scopedClient.child).toHaveBeenCalledWith({ headers: { [headerKey]: 'foo', - 'x-opaque-id': expect.any(String), }, }); }); @@ -326,32 +302,6 @@ describe('ClusterClient', () => { expect(scopedClient.child).toHaveBeenCalledWith({ headers: { [headerKey]: 'foo', - 'x-opaque-id': expect.any(String), - }, - }); - }); - - it('respect the precedence of x-opaque-id header over config headers', () => { - const config = createConfig({ - customHeaders: { - 'x-opaque-id': 'from config', - }, - }); - getAuthHeaders.mockReturnValue({}); - - const clusterClient = new ClusterClient(config, logger, 'custom-type', getAuthHeaders); - const request = httpServerMock.createKibanaRequest({ - headers: { foo: 'request' }, - kibanaRequestState: { requestId: 'from request', requestUuid: 'ignore-this-id' }, - }); - - clusterClient.asScoped(request); - - expect(scopedClient.child).toHaveBeenCalledTimes(1); - expect(scopedClient.child).toHaveBeenCalledWith({ - headers: { - ...DEFAULT_HEADERS, - 'x-opaque-id': 'from request', }, }); }); From 78fde491c7d82b917318a73f69618a8f97b52c39 Mon Sep 17 00:00:00 2001 From: restrry Date: Tue, 29 Jun 2021 16:23:54 +0300 Subject: [PATCH 20/36] update test plugin package.json --- .../plugins/core_plugin_execution_context/package.json | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/plugin_functional/plugins/core_plugin_execution_context/package.json b/test/plugin_functional/plugins/core_plugin_execution_context/package.json index 41f12a8a9e7852..4b932850cfa044 100644 --- a/test/plugin_functional/plugins/core_plugin_execution_context/package.json +++ b/test/plugin_functional/plugins/core_plugin_execution_context/package.json @@ -1,14 +1,13 @@ { - "name": "core_plugin_a", + "name": "core_plugin_execution_context", "version": "1.0.0", - "main": "target/test/plugin_functional/plugins/core_plugin_a", + "main": "target/test/plugin_functional/plugins/core_plugin_execution_context", "kibana": { - "version": "kibana", - "templateVersion": "1.0.0" + "version": "kibana" }, "license": "SSPL-1.0 OR Elastic License 2.0", "scripts": { "kbn": "node ../../../../scripts/kbn.js", "build": "rm -rf './target' && ../../../../node_modules/.bin/tsc" } -} \ No newline at end of file +} From 9030157771bd51c48face7d58a88ebf1b52ff88c Mon Sep 17 00:00:00 2001 From: restrry Date: Wed, 30 Jun 2021 12:07:41 +0300 Subject: [PATCH 21/36] fix type errors in the test mocks --- src/core/public/mocks.ts | 3 +++ src/core/server/mocks.ts | 1 + .../server/routes/integration_tests/stats.test.ts | 2 ++ .../create_es_client/create_apm_event_client/index.test.ts | 6 +++++- x-pack/plugins/xpack_legacy/server/routes/settings.test.ts | 2 ++ 5 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/core/public/mocks.ts b/src/core/public/mocks.ts index bd7623beba651e..63b94ea4ac4e35 100644 --- a/src/core/public/mocks.ts +++ b/src/core/public/mocks.ts @@ -25,6 +25,7 @@ import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; import { savedObjectsServiceMock } from './saved_objects/saved_objects_service.mock'; import { injectedMetadataServiceMock } from './injected_metadata/injected_metadata_service.mock'; import { deprecationsServiceMock } from './deprecations/deprecations_service.mock'; +import { executionContextServiceMock } from './execution_context/execution_context_service.mock'; export { chromeServiceMock } from './chrome/chrome_service.mock'; export { docLinksServiceMock } from './doc_links/doc_links_service.mock'; @@ -39,6 +40,7 @@ export { savedObjectsServiceMock } from './saved_objects/saved_objects_service.m export { scopedHistoryMock } from './application/scoped_history.mock'; export { applicationServiceMock } from './application/application_service.mock'; export { deprecationsServiceMock } from './deprecations/deprecations_service.mock'; +export { executionContextServiceMock } from './execution_context/execution_context_service.mock'; function createCoreSetupMock({ basePath = '', @@ -84,6 +86,7 @@ function createCoreStartMock({ basePath = '' } = {}) { getInjectedVar: injectedMetadataServiceMock.createStartContract().getInjectedVar, }, fatalErrors: fatalErrorsServiceMock.createStartContract(), + executionContext: executionContextServiceMock.createStartContract(), }; return mock; diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 9b909a4526d336..ff844f44aede06 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -52,6 +52,7 @@ export { capabilitiesServiceMock } from './capabilities/capabilities_service.moc export { coreUsageDataServiceMock } from './core_usage_data/core_usage_data_service.mock'; export { i18nServiceMock } from './i18n/i18n_service.mock'; export { deprecationsServiceMock } from './deprecations/deprecations_service.mock'; +export { executionContextServiceMock } from './execution_context/execution_context_service.mock'; type MockedPluginInitializerConfig = jest.Mocked['config']>; diff --git a/src/plugins/usage_collection/server/routes/integration_tests/stats.test.ts b/src/plugins/usage_collection/server/routes/integration_tests/stats.test.ts index 1409e4dd2c9086..8466093664d0d5 100644 --- a/src/plugins/usage_collection/server/routes/integration_tests/stats.test.ts +++ b/src/plugins/usage_collection/server/routes/integration_tests/stats.test.ts @@ -18,6 +18,7 @@ import { contextServiceMock, loggingSystemMock, metricsServiceMock, + executionContextServiceMock, } from '../../../../../core/server/mocks'; import { createHttpServer } from '../../../../../core/server/test_utils'; import { registerStatsRoute } from '../stats'; @@ -37,6 +38,7 @@ describe('/api/stats', () => { server = createHttpServer(); httpSetup = await server.setup({ context: contextServiceMock.createSetupContract(), + executionContext: executionContextServiceMock.createInternalSetupContract(), }); overallStatus$ = new BehaviorSubject({ level: ServiceStatusLevels.available, diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts index 8e82a189d75f35..a98bdab53cad99 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts @@ -5,7 +5,10 @@ * 2.0. */ -import { contextServiceMock } from 'src/core/server/mocks'; +import { + contextServiceMock, + executionContextServiceMock, +} from '../../../../../../../../src/core/server/mocks'; import { createHttpServer } from 'src/core/server/test_utils'; import supertest from 'supertest'; import { createApmEventClient } from '.'; @@ -23,6 +26,7 @@ describe('createApmEventClient', () => { it('cancels a search when a request is aborted', async () => { const { server: innerServer, createRouter } = await server.setup({ context: contextServiceMock.createSetupContract(), + executionContext: executionContextServiceMock.createInternalSetupContract(), }); const router = createRouter('/'); diff --git a/x-pack/plugins/xpack_legacy/server/routes/settings.test.ts b/x-pack/plugins/xpack_legacy/server/routes/settings.test.ts index 2034a4e5b74bab..57dcc924375cdf 100644 --- a/x-pack/plugins/xpack_legacy/server/routes/settings.test.ts +++ b/x-pack/plugins/xpack_legacy/server/routes/settings.test.ts @@ -14,6 +14,7 @@ import { contextServiceMock, elasticsearchServiceMock, savedObjectsServiceMock, + executionContextServiceMock, } from '../../../../../src/core/server/mocks'; import { createHttpServer } from '../../../../../src/core/server/test_utils'; import { registerSettingsRoute } from './settings'; @@ -48,6 +49,7 @@ describe('/api/settings', () => { }, }, }), + executionContext: executionContextServiceMock.createInternalSetupContract(), }); overallStatus$ = new BehaviorSubject({ From 10caddef4df3a8578499210ee7e522abaf34168f Mon Sep 17 00:00:00 2001 From: restrry Date: Wed, 30 Jun 2021 12:21:16 +0300 Subject: [PATCH 22/36] fix elasticsearch service tests --- .../server/elasticsearch/client/cluster_client.test.ts | 7 ++++++- .../server/elasticsearch/client/configure_client.test.ts | 2 +- .../server/elasticsearch/elasticsearch_service.test.ts | 7 +------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/core/server/elasticsearch/client/cluster_client.test.ts b/src/core/server/elasticsearch/client/cluster_client.test.ts index 062adc3540c37b..33a4aec6212da5 100644 --- a/src/core/server/elasticsearch/client/cluster_client.test.ts +++ b/src/core/server/elasticsearch/client/cluster_client.test.ts @@ -59,10 +59,15 @@ describe('ClusterClient', () => { new ClusterClient(config, logger, 'custom-type', getAuthHeaders); expect(configureClientMock).toHaveBeenCalledTimes(2); - expect(configureClientMock).toHaveBeenCalledWith(config, { logger, type: 'custom-type' }); expect(configureClientMock).toHaveBeenCalledWith(config, { logger, type: 'custom-type', + getExecutionContext: expect.any(Function), + }); + expect(configureClientMock).toHaveBeenCalledWith(config, { + logger, + type: 'custom-type', + getExecutionContext: expect.any(Function), scoped: true, }); }); diff --git a/src/core/server/elasticsearch/client/configure_client.test.ts b/src/core/server/elasticsearch/client/configure_client.test.ts index 2c3a203dbc49d8..924f1584c5f8ff 100644 --- a/src/core/server/elasticsearch/client/configure_client.test.ts +++ b/src/core/server/elasticsearch/client/configure_client.test.ts @@ -98,7 +98,7 @@ describe('configureClient', () => { const client = configureClient(config, { logger, type: 'test', scoped: false }); expect(ClientMock).toHaveBeenCalledTimes(1); - expect(ClientMock).toHaveBeenCalledWith(parsedOptions); + expect(ClientMock).toHaveBeenCalledWith(expect.objectContaining(parsedOptions)); expect(client).toBe(ClientMock.mock.results[0].value); }); diff --git a/src/core/server/elasticsearch/elasticsearch_service.test.ts b/src/core/server/elasticsearch/elasticsearch_service.test.ts index facb44eeab5ecd..791ae2ab7abaa8 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.test.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.test.ts @@ -276,12 +276,7 @@ describe('#start', () => { expect(clusterClient).toBe(mockClusterClientInstance); expect(MockClusterClient).toHaveBeenCalledTimes(1); - expect(MockClusterClient).toHaveBeenCalledWith( - expect.objectContaining(customConfig), - expect.objectContaining({ context: ['elasticsearch'] }), - 'custom-type', - expect.any(Function) - ); + expect(MockClusterClient.mock.calls[0][0]).toEqual(expect.objectContaining(customConfig)); }); it('creates a new client on each call', async () => { await elasticsearchService.setup(setupDeps); From 7e33503df552132ec8ce3a1ebee899532ada69a3 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Wed, 30 Jun 2021 14:05:25 +0300 Subject: [PATCH 23/36] add escaping to support non-ascii symbols in description field --- .../execution_context_container.test.ts | 68 +++++++++++++++++++ .../execution_context_container.ts | 10 +-- .../execution_context_service.mock.ts | 1 - .../execution_context_service.ts | 2 +- .../integration_tests/tracing.test.ts | 44 +++++++++--- .../core_plugins/execution_context.ts | 3 +- 6 files changed, 112 insertions(+), 16 deletions(-) create mode 100644 src/core/public/execution_context/execution_context_container.test.ts diff --git a/src/core/public/execution_context/execution_context_container.test.ts b/src/core/public/execution_context/execution_context_container.test.ts new file mode 100644 index 00000000000000..a4ee355ab40a4c --- /dev/null +++ b/src/core/public/execution_context/execution_context_container.test.ts @@ -0,0 +1,68 @@ +/* + * 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. + */ +import type { KibanaExecutionContext } from '../../types'; +import { + ExecutionContextContainer, + BAGGAGE_MAX_PER_NAME_VALUE_PAIRS, +} from './execution_context_container'; + +describe('KibanaExecutionContext', () => { + describe('toHeader', () => { + it('returns an escaped string representation of provided execution context', () => { + const context: KibanaExecutionContext = { + type: 'test-type', + name: 'test-name', + id: '42', + description: 'test-descripton', + }; + + const value = new ExecutionContextContainer(context).toHeader(); + expect(value).toMatchInlineSnapshot(` + Object { + "x-kbn-context": "%7B%22type%22%3A%22test-type%22%2C%22name%22%3A%22test-name%22%2C%22id%22%3A%2242%22%2C%22description%22%3A%22test-descripton%22%7D", + } + `); + }); + + it('trims a string representation of provided execution context if it is bigger max allowed size', () => { + const context: KibanaExecutionContext = { + type: 'test-type', + name: 'test-name', + id: '42', + description: 'long long test-descripton,'.repeat(1000), + }; + + const value = new ExecutionContextContainer(context).toHeader(); + expect(value).toMatchInlineSnapshot(` + Object { + "x-kbn-context": "%7B%22type%22%3A%22test-type%22%2C%22name%22%3A%22test-name%22%2C%22id%22%3A%2242%22%2C%22description%22%3A%22long%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test", + } + `); + + expect(new Blob(Object.values(value)).size).toBeLessThanOrEqual( + BAGGAGE_MAX_PER_NAME_VALUE_PAIRS + ); + }); + + it('escapes the string representation of provided execution context', () => { + const context: KibanaExecutionContext = { + type: 'test-type', + name: 'test-name', + id: '42', + description: 'описание', + }; + + const value = new ExecutionContextContainer(context).toHeader(); + expect(value).toMatchInlineSnapshot(` + Object { + "x-kbn-context": "%7B%22type%22%3A%22test-type%22%2C%22name%22%3A%22test-name%22%2C%22id%22%3A%2242%22%2C%22description%22%3A%22%D0%BE%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5%22%7D", + } + `); + }); + }); +}); diff --git a/src/core/public/execution_context/execution_context_container.ts b/src/core/public/execution_context/execution_context_container.ts index 16995c02043825..471fa5dda2925e 100644 --- a/src/core/public/execution_context/execution_context_container.ts +++ b/src/core/public/execution_context/execution_context_container.ts @@ -9,11 +9,11 @@ import type { KibanaExecutionContext } from '../../types'; // Switch to the standard Baggage header // https://github.com/elastic/apm-agent-rum-js/issues/1040 -export const BAGGAGE_HEADER = 'x-kbn-context'; +const BAGGAGE_HEADER = 'x-kbn-context'; // Maximum number of bytes per a single name-value pair allowed by w3c spec // https://w3c.github.io/baggage/ -const BAGGAGE_MAX_PER_NAME_VALUE_PAIRS = 4096; +export const BAGGAGE_MAX_PER_NAME_VALUE_PAIRS = 4096; // a single character can use up to 4 bytes const MAX_BAGGAGE_LENGTH = BAGGAGE_MAX_PER_NAME_VALUE_PAIRS / 4; @@ -28,8 +28,10 @@ export class ExecutionContextContainer { constructor(context: Readonly) { this.#context = context; } - toString(): string { - return enforceMaxLength(JSON.stringify(this.#context)); + private toString(): string { + const value = JSON.stringify(this.#context); + // escape content as the description property might contain non-ASCII symbols + return enforceMaxLength(encodeURIComponent(value)); } toHeader() { return { [BAGGAGE_HEADER]: this.toString() }; diff --git a/src/core/public/execution_context/execution_context_service.mock.ts b/src/core/public/execution_context/execution_context_service.mock.ts index 8956653d285ac0..c3a5d5b7f58340 100644 --- a/src/core/public/execution_context/execution_context_service.mock.ts +++ b/src/core/public/execution_context/execution_context_service.mock.ts @@ -11,7 +11,6 @@ import type { ExecutionContextContainer } from './execution_context_container'; const creteContainerMock = () => { const mock: jest.Mocked> = { - toString: jest.fn(), toHeader: jest.fn(), }; return mock; diff --git a/src/core/server/execution_context/execution_context_service.ts b/src/core/server/execution_context/execution_context_service.ts index 93422d382cb5dc..90704c45fd9b71 100644 --- a/src/core/server/execution_context/execution_context_service.ts +++ b/src/core/server/execution_context/execution_context_service.ts @@ -64,7 +64,7 @@ export type ExecutionContextStart = ExecutionContextSetup; function parseHeader(header?: string): KibanaExecutionContext | undefined { if (!header) return undefined; try { - return JSON.parse(header); + return JSON.parse(decodeURIComponent(header)); } catch (e) { return undefined; } diff --git a/src/core/server/execution_context/integration_tests/tracing.test.ts b/src/core/server/execution_context/integration_tests/tracing.test.ts index 9572c61515aa3d..b6013c18c37d7e 100644 --- a/src/core/server/execution_context/integration_tests/tracing.test.ts +++ b/src/core/server/execution_context/integration_tests/tracing.test.ts @@ -6,10 +6,7 @@ * Side Public License, v 1. */ -import { - BAGGAGE_HEADER, - ExecutionContextContainer, -} from '../../../public/execution_context/execution_context_container'; +import { ExecutionContextContainer } from '../../../public/execution_context/execution_context_container'; import * as kbnTestServer from '../../../test_helpers/kbn_server'; const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); @@ -238,7 +235,7 @@ describe('trace', () => { await root.start(); const response = await kbnTestServer.request .get(root, '/execution-context') - .set(BAGGAGE_HEADER, new ExecutionContextContainer(parentContext).toString()) + .set(new ExecutionContextContainer(parentContext).toHeader()) .expect(200); expect(response.body).toEqual({ ...parentContext, requestId: expect.any(String) }); @@ -293,7 +290,7 @@ describe('trace', () => { await root.start(); const response = await kbnTestServer.request .get(root, '/execution-context') - .set(BAGGAGE_HEADER, new ExecutionContextContainer(parentContext).toString()) + .set(new ExecutionContextContainer(parentContext).toHeader()) .expect(200); expect(response.body).toEqual({ ...parentContext, requestId: expect.any(String) }); @@ -319,7 +316,7 @@ describe('trace', () => { const response = await kbnTestServer.request .get(root, '/execution-context') - .set(BAGGAGE_HEADER, new ExecutionContextContainer(parentContext).toString()) + .set(new ExecutionContextContainer(parentContext).toHeader()) .expect(200); const header = response.body['x-opaque-id']; @@ -340,7 +337,7 @@ describe('trace', () => { const response = await kbnTestServer.request .get(root, '/execution-context') - .set(BAGGAGE_HEADER, new ExecutionContextContainer(parentContext).toString()) + .set(new ExecutionContextContainer(parentContext).toHeader()) .expect(200); const header = response.body['x-opaque-id']; @@ -368,7 +365,7 @@ describe('trace', () => { const response = await kbnTestServer.request .get(root, '/execution-context') - .set(BAGGAGE_HEADER, new ExecutionContextContainer(parentContext).toString()) + .set(new ExecutionContextContainer(parentContext).toHeader()) .expect(200); const header = response.body['x-opaque-id']; @@ -397,5 +394,34 @@ describe('trace', () => { const header = response.body['x-opaque-id']; expect(header).toBe('my-opaque-id;kibana:test-type:42'); }); + + it('does not break on non-ASCII characters within execution context', async () => { + const { http, executionContext } = await root.setup(); + const { createRouter } = http; + + const router = createRouter(''); + const ctx = { + type: 'test-type', + name: 'test-name', + id: '42', + description: 'какое-то описание', + }; + router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { + executionContext.set(ctx); + const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping(); + return res.ok({ body: headers || {} }); + }); + + await root.start(); + + const myOpaqueId = 'my-opaque-id'; + const response = await kbnTestServer.request + .get(root, '/execution-context') + .set('x-opaque-id', myOpaqueId) + .expect(200); + + const header = response.body['x-opaque-id']; + expect(header).toBe('my-opaque-id;kibana:test-type:42'); + }); }); }); diff --git a/test/plugin_functional/test_suites/core_plugins/execution_context.ts b/test/plugin_functional/test_suites/core_plugins/execution_context.ts index 1a48a51ca94f68..2f80423e47739a 100644 --- a/test/plugin_functional/test_suites/core_plugins/execution_context.ts +++ b/test/plugin_functional/test_suites/core_plugins/execution_context.ts @@ -29,7 +29,8 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide type: 'execution_context_app', name: 'Execution context app', id: '42', - description: 'a request initiated by Execution context app', + // add a non-ASCII symbols to make it doesn't break the context propagation mechanism + description: 'какое-то странное описание', }); const result = await coreStart.http.get('/execution_context/pass', { From 32706afe761c9439ad288bfb76066337aa39b384 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Wed, 30 Jun 2021 15:51:32 +0300 Subject: [PATCH 24/36] improve test coverage --- src/core/public/core_system.test.mocks.ts | 9 ++ src/core/public/core_system.test.ts | 19 +++++ src/core/public/core_system.ts | 1 + .../execution_context_service.mock.ts | 8 ++ .../execution_context_container.test.ts | 72 ++++++++++++++++ .../execution_context_container.ts | 17 ++++ .../execution_context_service.test.ts | 85 +++++++++++++++++++ .../execution_context_service.ts | 20 +---- 8 files changed, 214 insertions(+), 17 deletions(-) create mode 100644 src/core/server/execution_context/execution_context_container.test.ts create mode 100644 src/core/server/execution_context/execution_context_service.test.ts diff --git a/src/core/public/core_system.test.mocks.ts b/src/core/public/core_system.test.mocks.ts index afb8aec31cccd8..c80c2e3f497750 100644 --- a/src/core/public/core_system.test.mocks.ts +++ b/src/core/public/core_system.test.mocks.ts @@ -19,6 +19,7 @@ import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; import { docLinksServiceMock } from './doc_links/doc_links_service.mock'; import { renderingServiceMock } from './rendering/rendering_service.mock'; import { integrationsServiceMock } from './integrations/integrations_service.mock'; +import { executionContextServiceMock } from './execution_context/execution_context_service.mock'; import { coreAppMock } from './core_app/core_app.mock'; export const MockInjectedMetadataService = injectedMetadataServiceMock.create(); @@ -111,6 +112,14 @@ jest.doMock('./integrations', () => ({ IntegrationsService: IntegrationsServiceConstructor, })); +export const MockExecutionContextService = executionContextServiceMock.create(); +export const ExecutionContextServiceConstructor = jest + .fn() + .mockImplementation(() => MockExecutionContextService); +jest.doMock('./execution_context', () => ({ + ExecutionContextService: ExecutionContextServiceConstructor, +})); + export const MockCoreApp = coreAppMock.create(); export const CoreAppConstructor = jest.fn().mockImplementation(() => MockCoreApp); jest.doMock('./core_app', () => ({ diff --git a/src/core/public/core_system.test.ts b/src/core/public/core_system.test.ts index 8ead0f50785bdc..efafb25da27ee1 100644 --- a/src/core/public/core_system.test.ts +++ b/src/core/public/core_system.test.ts @@ -30,6 +30,7 @@ import { RenderingServiceConstructor, IntegrationsServiceConstructor, MockIntegrationsService, + MockExecutionContextService, CoreAppConstructor, MockCoreApp, } from './core_system.test.mocks'; @@ -182,6 +183,11 @@ describe('#setup()', () => { await setupCore(); expect(MockCoreApp.setup).toHaveBeenCalledTimes(1); }); + + it('calls executionContext.setup()', async () => { + await setupCore(); + expect(MockExecutionContextService.setup).toHaveBeenCalledTimes(1); + }); }); describe('#start()', () => { @@ -269,6 +275,11 @@ describe('#start()', () => { await startCore(); expect(MockCoreApp.start).toHaveBeenCalledTimes(1); }); + + it('calls executionContext.start()', async () => { + await startCore(); + expect(MockExecutionContextService.start).toHaveBeenCalledTimes(1); + }); }); describe('#stop()', () => { @@ -327,6 +338,14 @@ describe('#stop()', () => { expect(MockCoreApp.stop).toHaveBeenCalled(); }); + it('calls executionContext.stop()', () => { + const coreSystem = createCoreSystem(); + + expect(MockExecutionContextService.stop).not.toHaveBeenCalled(); + coreSystem.stop(); + expect(MockExecutionContextService.stop).toHaveBeenCalled(); + }); + it('clears the rootDomElement', async () => { const rootDomElement = document.createElement('div'); const coreSystem = createCoreSystem({ diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index 6b5f44f10dc272..43e7d443f5c00e 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -266,6 +266,7 @@ export class CoreSystem { this.i18n.stop(); this.application.stop(); this.deprecations.stop(); + this.executionContext.stop(); this.rootDomElement.textContent = ''; } } diff --git a/src/core/public/execution_context/execution_context_service.mock.ts b/src/core/public/execution_context/execution_context_service.mock.ts index c3a5d5b7f58340..839beaed7c1fed 100644 --- a/src/core/public/execution_context/execution_context_service.mock.ts +++ b/src/core/public/execution_context/execution_context_service.mock.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ import type { PublicMethodsOf } from '@kbn/utility-types'; +import type { Plugin } from 'src/core/public'; import type { ExecutionContextServiceStart } from './execution_context_service'; import type { ExecutionContextContainer } from './execution_context_container'; @@ -22,6 +23,13 @@ const createStartContractMock = () => { return mock; }; +const createMock = (): jest.Mocked => ({ + setup: jest.fn(), + start: jest.fn(), + stop: jest.fn(), +}); + export const executionContextServiceMock = { + create: createMock, createStartContract: createStartContractMock, }; diff --git a/src/core/server/execution_context/execution_context_container.test.ts b/src/core/server/execution_context/execution_context_container.test.ts new file mode 100644 index 00000000000000..cc3a8916474669 --- /dev/null +++ b/src/core/server/execution_context/execution_context_container.test.ts @@ -0,0 +1,72 @@ +/* + * 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. + */ +import type { KibanaServerExecutionContext } from './execution_context_service'; +import { + ExecutionContextContainer, + getParentContextFrom, + BAGGAGE_HEADER, +} from './execution_context_container'; + +describe('KibanaExecutionContext', () => { + describe('toString', () => { + it('returns a string representation of provided execution context', () => { + const context: KibanaServerExecutionContext = { + type: 'test-type', + name: 'test-name', + id: '42', + description: 'test-descripton', + requestId: '1234-5678', + }; + + const value = new ExecutionContextContainer(context).toString(); + expect(value).toMatchInlineSnapshot(`"1234-5678;kibana:test-type:42"`); + }); + + it('returns a limited representation if optional properties are omitted', () => { + const context: KibanaServerExecutionContext = { + requestId: '1234-5678', + }; + + const value = new ExecutionContextContainer(context).toString(); + expect(value).toMatchInlineSnapshot(`"1234-5678"`); + }); + }); + + describe('toJSON', () => { + it('returns a context object', () => { + const context: KibanaServerExecutionContext = { + type: 'test-type', + name: 'test-name', + id: '42', + description: 'test-descripton', + requestId: '1234-5678', + }; + + const value = new ExecutionContextContainer(context).toJSON(); + expect(value).toBe(context); + }); + }); +}); + +describe('getParentContextFrom', () => { + it('decodes provided header', () => { + const ctx = { id: '42' }; + const header = encodeURIComponent(JSON.stringify(ctx)); + expect(getParentContextFrom({ [BAGGAGE_HEADER]: header })).toEqual(ctx); + }); + + it('does not throw an exception if given not a valid value', () => { + expect(getParentContextFrom({ [BAGGAGE_HEADER]: 'value' })).toBeUndefined(); + expect(getParentContextFrom({ [BAGGAGE_HEADER]: '' })).toBeUndefined(); + expect(getParentContextFrom({})).toBeUndefined(); + + const ctx = { id: '42' }; + const header = encodeURIComponent(JSON.stringify(ctx)); + expect(getParentContextFrom({ [BAGGAGE_HEADER]: header.slice(0, -2) })).toBeUndefined(); + }); +}); diff --git a/src/core/server/execution_context/execution_context_container.ts b/src/core/server/execution_context/execution_context_container.ts index 0c6a73f4d54ca7..43b89a6ac4ff38 100644 --- a/src/core/server/execution_context/execution_context_container.ts +++ b/src/core/server/execution_context/execution_context_container.ts @@ -6,11 +6,28 @@ * Side Public License, v 1. */ import type { KibanaServerExecutionContext } from './execution_context_service'; +import type { KibanaExecutionContext } from '../../types'; // Switch to the standard Baggage header. blocked by // https://github.com/elastic/apm-agent-nodejs/issues/2102 export const BAGGAGE_HEADER = 'x-kbn-context'; +export function getParentContextFrom( + headers: Record +): KibanaExecutionContext | undefined { + const header = headers[BAGGAGE_HEADER]; + return parseHeader(header); +} + +function parseHeader(header?: string): KibanaExecutionContext | undefined { + if (!header) return undefined; + try { + return JSON.parse(decodeURIComponent(header)); + } catch (e) { + return undefined; + } +} + export class ExecutionContextContainer { readonly #context: Readonly; constructor(context: Readonly) { diff --git a/src/core/server/execution_context/execution_context_service.test.ts b/src/core/server/execution_context/execution_context_service.test.ts new file mode 100644 index 00000000000000..26f6f2a2c18b74 --- /dev/null +++ b/src/core/server/execution_context/execution_context_service.test.ts @@ -0,0 +1,85 @@ +/* + * 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. + */ +import { + ExecutionContextService, + InternalExecutionContextSetup, + KibanaServerExecutionContext, +} from './execution_context_service'; +import { mockCoreContext } from '../core_context.mock'; + +const delay = (ms: number = 100) => new Promise((resolve) => setTimeout(resolve, ms)); +describe('ExecutionContextService', () => { + describe('setup', () => { + let service: InternalExecutionContextSetup; + const core = mockCoreContext.create(); + beforeEach(() => { + service = new ExecutionContextService(core).setup(); + }); + + it('sets and gets a value in async context', async () => { + const chainA = Promise.resolve().then(async () => { + service.set({ + requestId: '0000', + }); + await delay(500); + return service.get(); + }); + + const chainB = Promise.resolve().then(async () => { + service.set({ + requestId: '1111', + }); + await delay(100); + return service.get(); + }); + + expect( + await Promise.all([chainA, chainB]).then((results) => + results.map((result) => result?.toJSON()) + ) + ).toEqual([ + { + requestId: '0000', + }, + { + requestId: '1111', + }, + ]); + }); + + it('sets and resets a value in async context', async () => { + const chainA = Promise.resolve().then(async () => { + service.set({ + requestId: '0000', + }); + await delay(500); + service.reset(); + return service.get(); + }); + + const chainB = Promise.resolve().then(async () => { + service.set({ + requestId: '1111', + }); + await delay(100); + return service.get(); + }); + + expect( + await Promise.all([chainA, chainB]).then((results) => + results.map((result) => result?.toJSON()) + ) + ).toEqual([ + undefined, + { + requestId: '1111', + }, + ]); + }); + }); +}); diff --git a/src/core/server/execution_context/execution_context_service.ts b/src/core/server/execution_context/execution_context_service.ts index 90704c45fd9b71..287aedb6d56075 100644 --- a/src/core/server/execution_context/execution_context_service.ts +++ b/src/core/server/execution_context/execution_context_service.ts @@ -11,7 +11,7 @@ import type { CoreService, KibanaExecutionContext } from '../../types'; import type { CoreContext } from '../core_context'; import type { Logger } from '../logging'; -import { BAGGAGE_HEADER, ExecutionContextContainer } from './execution_context_container'; +import { ExecutionContextContainer, getParentContextFrom } from './execution_context_container'; /** * @public @@ -61,15 +61,6 @@ export interface ExecutionContextSetup { */ export type ExecutionContextStart = ExecutionContextSetup; -function parseHeader(header?: string): KibanaExecutionContext | undefined { - if (!header) return undefined; - try { - return JSON.parse(decodeURIComponent(header)); - } catch (e) { - return undefined; - } -} - export class ExecutionContextService implements CoreService { private readonly log: Logger; @@ -82,7 +73,7 @@ export class ExecutionContextService setup(): InternalExecutionContextSetup { return { - getParentContextFrom: this.getParentContextFrom.bind(this), + getParentContextFrom, set: this.set.bind(this), reset: this.reset.bind(this), get: this.get.bind(this), @@ -91,7 +82,7 @@ export class ExecutionContextService start(): InternalExecutionContextStart { return { - getParentContextFrom: this.getParentContextFrom.bind(this), + getParentContextFrom, set: this.set.bind(this), reset: this.reset.bind(this), get: this.get.bind(this), @@ -99,11 +90,6 @@ export class ExecutionContextService } stop() {} - private getParentContextFrom(headers: Record) { - const header = headers[BAGGAGE_HEADER]; - return parseHeader(header); - } - private set(context: KibanaServerExecutionContext) { const prevValue = this.asyncLocalStorage.getStore(); // merges context objects shallowly. repeats the deafult logic of apm.setCustomContext(ctx) From e7fc6336b9912b062f817dd4786b4523362c7f03 Mon Sep 17 00:00:00 2001 From: restrry Date: Wed, 30 Jun 2021 15:54:12 +0300 Subject: [PATCH 25/36] update docs --- ...-plugin-core-public.corestart.executioncontext.md | 12 ++++++++++++ .../public/kibana-plugin-core-public.corestart.md | 1 + ...na-plugin-core-public.httpfetchoptions.context.md | 11 +++++++++++ .../kibana-plugin-core-public.httpfetchoptions.md | 1 + ...-plugin-core-server.coresetup.executioncontext.md | 12 ++++++++++++ .../server/kibana-plugin-core-server.coresetup.md | 1 + ...-plugin-core-server.corestart.executioncontext.md | 12 ++++++++++++ .../server/kibana-plugin-core-server.corestart.md | 1 + src/core/public/public.api.md | 11 ++++++++++- src/core/server/server.api.md | 10 ++++++++++ 10 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 docs/development/core/public/kibana-plugin-core-public.corestart.executioncontext.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.httpfetchoptions.context.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.coresetup.executioncontext.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.corestart.executioncontext.md diff --git a/docs/development/core/public/kibana-plugin-core-public.corestart.executioncontext.md b/docs/development/core/public/kibana-plugin-core-public.corestart.executioncontext.md new file mode 100644 index 00000000000000..a75583f5f0d3e2 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.corestart.executioncontext.md @@ -0,0 +1,12 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [CoreStart](./kibana-plugin-core-public.corestart.md) > [executionContext](./kibana-plugin-core-public.corestart.executioncontext.md) + +## CoreStart.executionContext property + + +Signature: + +```typescript +executionContext: ExecutionContextServiceStart; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.corestart.md b/docs/development/core/public/kibana-plugin-core-public.corestart.md index 6ad9adca53ef5c..573bc7220873e9 100644 --- a/docs/development/core/public/kibana-plugin-core-public.corestart.md +++ b/docs/development/core/public/kibana-plugin-core-public.corestart.md @@ -20,6 +20,7 @@ export interface CoreStart | [chrome](./kibana-plugin-core-public.corestart.chrome.md) | ChromeStart | [ChromeStart](./kibana-plugin-core-public.chromestart.md) | | [deprecations](./kibana-plugin-core-public.corestart.deprecations.md) | DeprecationsServiceStart | [DeprecationsServiceStart](./kibana-plugin-core-public.deprecationsservicestart.md) | | [docLinks](./kibana-plugin-core-public.corestart.doclinks.md) | DocLinksStart | [DocLinksStart](./kibana-plugin-core-public.doclinksstart.md) | +| [executionContext](./kibana-plugin-core-public.corestart.executioncontext.md) | ExecutionContextServiceStart | | | [fatalErrors](./kibana-plugin-core-public.corestart.fatalerrors.md) | FatalErrorsStart | [FatalErrorsStart](./kibana-plugin-core-public.fatalerrorsstart.md) | | [http](./kibana-plugin-core-public.corestart.http.md) | HttpStart | [HttpStart](./kibana-plugin-core-public.httpstart.md) | | [i18n](./kibana-plugin-core-public.corestart.i18n.md) | I18nStart | [I18nStart](./kibana-plugin-core-public.i18nstart.md) | diff --git a/docs/development/core/public/kibana-plugin-core-public.httpfetchoptions.context.md b/docs/development/core/public/kibana-plugin-core-public.httpfetchoptions.context.md new file mode 100644 index 00000000000000..a0d75b7408ec37 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.httpfetchoptions.context.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [HttpFetchOptions](./kibana-plugin-core-public.httpfetchoptions.md) > [context](./kibana-plugin-core-public.httpfetchoptions.context.md) + +## HttpFetchOptions.context property + +Signature: + +```typescript +context?: ExecutionContextContainer; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.httpfetchoptions.md b/docs/development/core/public/kibana-plugin-core-public.httpfetchoptions.md index 745020bb60714c..6ae819b5a1714a 100644 --- a/docs/development/core/public/kibana-plugin-core-public.httpfetchoptions.md +++ b/docs/development/core/public/kibana-plugin-core-public.httpfetchoptions.md @@ -18,6 +18,7 @@ export interface HttpFetchOptions extends HttpRequestInit | --- | --- | --- | | [asResponse](./kibana-plugin-core-public.httpfetchoptions.asresponse.md) | boolean | When true the return type of [HttpHandler](./kibana-plugin-core-public.httphandler.md) will be an [HttpResponse](./kibana-plugin-core-public.httpresponse.md) with detailed request and response information. When false, the return type will just be the parsed response body. Defaults to false. | | [asSystemRequest](./kibana-plugin-core-public.httpfetchoptions.assystemrequest.md) | boolean | Whether or not the request should include the "system request" header to differentiate an end user request from Kibana internal request. Can be read on the server-side using KibanaRequest\#isSystemRequest. Defaults to false. | +| [context](./kibana-plugin-core-public.httpfetchoptions.context.md) | ExecutionContextContainer | | | [headers](./kibana-plugin-core-public.httpfetchoptions.headers.md) | HttpHeadersInit | Headers to send with the request. See [HttpHeadersInit](./kibana-plugin-core-public.httpheadersinit.md). | | [prependBasePath](./kibana-plugin-core-public.httpfetchoptions.prependbasepath.md) | boolean | Whether or not the request should automatically prepend the basePath. Defaults to true. | | [query](./kibana-plugin-core-public.httpfetchoptions.query.md) | HttpFetchQuery | The query string for an HTTP request. See [HttpFetchQuery](./kibana-plugin-core-public.httpfetchquery.md). | diff --git a/docs/development/core/server/kibana-plugin-core-server.coresetup.executioncontext.md b/docs/development/core/server/kibana-plugin-core-server.coresetup.executioncontext.md new file mode 100644 index 00000000000000..473cfe2bbb68ec --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.coresetup.executioncontext.md @@ -0,0 +1,12 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CoreSetup](./kibana-plugin-core-server.coresetup.md) > [executionContext](./kibana-plugin-core-server.coresetup.executioncontext.md) + +## CoreSetup.executionContext property + + +Signature: + +```typescript +executionContext: ExecutionContextSetup; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.coresetup.md b/docs/development/core/server/kibana-plugin-core-server.coresetup.md index b37ac80db87d64..98d5ec6f36cc90 100644 --- a/docs/development/core/server/kibana-plugin-core-server.coresetup.md +++ b/docs/development/core/server/kibana-plugin-core-server.coresetup.md @@ -20,6 +20,7 @@ export interface CoreSetupContextSetup | [ContextSetup](./kibana-plugin-core-server.contextsetup.md) | | [deprecations](./kibana-plugin-core-server.coresetup.deprecations.md) | DeprecationsServiceSetup | [DeprecationsServiceSetup](./kibana-plugin-core-server.deprecationsservicesetup.md) | | [elasticsearch](./kibana-plugin-core-server.coresetup.elasticsearch.md) | ElasticsearchServiceSetup | [ElasticsearchServiceSetup](./kibana-plugin-core-server.elasticsearchservicesetup.md) | +| [executionContext](./kibana-plugin-core-server.coresetup.executioncontext.md) | ExecutionContextSetup | | | [getStartServices](./kibana-plugin-core-server.coresetup.getstartservices.md) | StartServicesAccessor<TPluginsStart, TStart> | [StartServicesAccessor](./kibana-plugin-core-server.startservicesaccessor.md) | | [http](./kibana-plugin-core-server.coresetup.http.md) | HttpServiceSetup & {
resources: HttpResources;
} | [HttpServiceSetup](./kibana-plugin-core-server.httpservicesetup.md) | | [i18n](./kibana-plugin-core-server.coresetup.i18n.md) | I18nServiceSetup | [I18nServiceSetup](./kibana-plugin-core-server.i18nservicesetup.md) | diff --git a/docs/development/core/server/kibana-plugin-core-server.corestart.executioncontext.md b/docs/development/core/server/kibana-plugin-core-server.corestart.executioncontext.md new file mode 100644 index 00000000000000..f8d50404c8d898 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.corestart.executioncontext.md @@ -0,0 +1,12 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CoreStart](./kibana-plugin-core-server.corestart.md) > [executionContext](./kibana-plugin-core-server.corestart.executioncontext.md) + +## CoreStart.executionContext property + + +Signature: + +```typescript +executionContext: ExecutionContextStart; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.corestart.md b/docs/development/core/server/kibana-plugin-core-server.corestart.md index f98088648689f7..03aeccdb6b44af 100644 --- a/docs/development/core/server/kibana-plugin-core-server.corestart.md +++ b/docs/development/core/server/kibana-plugin-core-server.corestart.md @@ -18,6 +18,7 @@ export interface CoreStart | --- | --- | --- | | [capabilities](./kibana-plugin-core-server.corestart.capabilities.md) | CapabilitiesStart | [CapabilitiesStart](./kibana-plugin-core-server.capabilitiesstart.md) | | [elasticsearch](./kibana-plugin-core-server.corestart.elasticsearch.md) | ElasticsearchServiceStart | [ElasticsearchServiceStart](./kibana-plugin-core-server.elasticsearchservicestart.md) | +| [executionContext](./kibana-plugin-core-server.corestart.executioncontext.md) | ExecutionContextStart | | | [http](./kibana-plugin-core-server.corestart.http.md) | HttpServiceStart | [HttpServiceStart](./kibana-plugin-core-server.httpservicestart.md) | | [metrics](./kibana-plugin-core-server.corestart.metrics.md) | MetricsServiceStart | [MetricsServiceStart](./kibana-plugin-core-server.metricsservicestart.md) | | [savedObjects](./kibana-plugin-core-server.corestart.savedobjects.md) | SavedObjectsServiceStart | [SavedObjectsServiceStart](./kibana-plugin-core-server.savedobjectsservicestart.md) | diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index f18dfb02fd41da..ad3364ac93afc6 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -432,6 +432,11 @@ export interface CoreStart { deprecations: DeprecationsServiceStart; // (undocumented) docLinks: DocLinksStart; + // Warning: (ae-forgotten-export) The symbol "ExecutionContextServiceStart" needs to be exported by the entry point index.d.ts + // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "ExecutionContextServiceStart" + // + // (undocumented) + executionContext: ExecutionContextServiceStart; // (undocumented) fatalErrors: FatalErrorsStart; // (undocumented) @@ -752,6 +757,10 @@ export class HttpFetchError extends Error implements IHttpFetchError { export interface HttpFetchOptions extends HttpRequestInit { asResponse?: boolean; asSystemRequest?: boolean; + // Warning: (ae-forgotten-export) The symbol "ExecutionContextContainer" needs to be exported by the entry point index.d.ts + // + // (undocumented) + context?: ExecutionContextContainer; headers?: HttpHeadersInit; prependBasePath?: boolean; query?: HttpFetchQuery; @@ -1663,6 +1672,6 @@ export interface UserProvidedValues { // Warnings were encountered during analysis: // -// src/core/public/core_system.ts:168:21 - (ae-forgotten-export) The symbol "InternalApplicationStart" needs to be exported by the entry point index.d.ts +// src/core/public/core_system.ts:172:21 - (ae-forgotten-export) The symbol "InternalApplicationStart" needs to be exported by the entry point index.d.ts ``` diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 13ec594df90750..f659a26af0580b 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -522,6 +522,11 @@ export interface CoreSetup; // (undocumented) @@ -550,6 +555,11 @@ export interface CoreStart { coreUsageData: CoreUsageDataStart; // (undocumented) elasticsearch: ElasticsearchServiceStart; + // Warning: (ae-forgotten-export) The symbol "ExecutionContextStart" needs to be exported by the entry point index.d.ts + // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "ExecutionContextStart" + // + // (undocumented) + executionContext: ExecutionContextStart; // (undocumented) http: HttpServiceStart; // (undocumented) From 50b2da84b09531bbca8d98352afd650c3ea2e867 Mon Sep 17 00:00:00 2001 From: restrry Date: Wed, 30 Jun 2021 16:09:09 +0300 Subject: [PATCH 26/36] remove unnecessary import --- .../server/execution_context/execution_context_service.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/server/execution_context/execution_context_service.test.ts b/src/core/server/execution_context/execution_context_service.test.ts index 26f6f2a2c18b74..b1cc9fc6f8bf4b 100644 --- a/src/core/server/execution_context/execution_context_service.test.ts +++ b/src/core/server/execution_context/execution_context_service.test.ts @@ -8,7 +8,6 @@ import { ExecutionContextService, InternalExecutionContextSetup, - KibanaServerExecutionContext, } from './execution_context_service'; import { mockCoreContext } from '../core_context.mock'; From dafe15a74533901070659a206c14d6d2c3f236ce Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Mon, 5 Jul 2021 17:50:04 +0300 Subject: [PATCH 27/36] update docs --- ...-core-public.corestart.executioncontext.md | 1 + .../kibana-plugin-core-public.corestart.md | 2 +- ...lic.executioncontextservicestart.create.md | 19 ++++++++++ ...ore-public.executioncontextservicestart.md | 25 ++++++++++++ ...in-core-public.httpfetchoptions.context.md | 2 +- ...ana-plugin-core-public.httpfetchoptions.md | 2 +- ...-core-public.iexecutioncontextcontainer.md | 19 ++++++++++ ...lic.iexecutioncontextcontainer.toheader.md | 11 ++++++ ...blic.kibanaexecutioncontext.description.md | 13 +++++++ ...n-core-public.kibanaexecutioncontext.id.md | 13 +++++++ ...ugin-core-public.kibanaexecutioncontext.md | 23 +++++++++++ ...core-public.kibanaexecutioncontext.name.md | 13 +++++++ ...core-public.kibanaexecutioncontext.type.md | 13 +++++++ ...-core-public.kibanaexecutioncontext.url.md | 13 +++++++ .../core/public/kibana-plugin-core-public.md | 3 ++ ...-core-server.coresetup.executioncontext.md | 1 + .../kibana-plugin-core-server.coresetup.md | 2 +- ...-core-server.corestart.executioncontext.md | 1 + .../kibana-plugin-core-server.corestart.md | 2 +- ...n-core-server.executioncontextsetup.get.md | 17 +++++++++ ...lugin-core-server.executioncontextsetup.md | 20 ++++++++++ ...n-core-server.executioncontextsetup.set.md | 24 ++++++++++++ ...lugin-core-server.executioncontextstart.md | 12 ++++++ ...-core-server.iexecutioncontextcontainer.md | 20 ++++++++++ ...erver.iexecutioncontextcontainer.tojson.md | 15 ++++++++ ...ver.iexecutioncontextcontainer.tostring.md | 15 ++++++++ ...rver.kibanaexecutioncontext.description.md | 13 +++++++ ...n-core-server.kibanaexecutioncontext.id.md | 13 +++++++ ...ugin-core-server.kibanaexecutioncontext.md | 23 +++++++++++ ...core-server.kibanaexecutioncontext.name.md | 13 +++++++ ...core-server.kibanaexecutioncontext.type.md | 13 +++++++ ...-core-server.kibanaexecutioncontext.url.md | 13 +++++++ ...ore-server.kibanaserverexecutioncontext.md | 19 ++++++++++ ....kibanaserverexecutioncontext.requestid.md | 11 ++++++ .../core/server/kibana-plugin-core-server.md | 5 +++ .../execution_context_container.ts | 9 ++++- .../execution_context_service.ts | 10 ++++- src/core/public/execution_context/index.ts | 3 +- src/core/public/http/types.ts | 4 +- src/core/public/index.ts | 6 +++ src/core/public/public.api.md | 27 ++++++++++--- .../elasticsearch/client/cluster_client.ts | 4 +- .../elasticsearch/client/configure_client.ts | 4 +- .../execution_context_container.ts | 10 ++++- .../execution_context_service.ts | 16 +++++--- src/core/server/execution_context/index.ts | 4 +- src/core/server/index.ts | 12 +++++- src/core/server/server.api.md | 38 ++++++++++++++++--- src/core/types/execution_context.ts | 2 +- 49 files changed, 535 insertions(+), 38 deletions(-) create mode 100644 docs/development/core/public/kibana-plugin-core-public.executioncontextservicestart.create.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.executioncontextservicestart.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.iexecutioncontextcontainer.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.iexecutioncontextcontainer.toheader.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.description.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.id.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.name.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.type.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.url.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.executioncontextsetup.get.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.executioncontextsetup.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.executioncontextsetup.set.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.executioncontextstart.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.iexecutioncontextcontainer.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.iexecutioncontextcontainer.tojson.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.iexecutioncontextcontainer.tostring.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.description.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.id.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.name.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.type.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.url.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.kibanaserverexecutioncontext.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.kibanaserverexecutioncontext.requestid.md diff --git a/docs/development/core/public/kibana-plugin-core-public.corestart.executioncontext.md b/docs/development/core/public/kibana-plugin-core-public.corestart.executioncontext.md index a75583f5f0d3e2..66c5f3efa2d847 100644 --- a/docs/development/core/public/kibana-plugin-core-public.corestart.executioncontext.md +++ b/docs/development/core/public/kibana-plugin-core-public.corestart.executioncontext.md @@ -4,6 +4,7 @@ ## CoreStart.executionContext property +[ExecutionContextServiceStart](./kibana-plugin-core-public.executioncontextservicestart.md) Signature: diff --git a/docs/development/core/public/kibana-plugin-core-public.corestart.md b/docs/development/core/public/kibana-plugin-core-public.corestart.md index 573bc7220873e9..df1929b1f20abe 100644 --- a/docs/development/core/public/kibana-plugin-core-public.corestart.md +++ b/docs/development/core/public/kibana-plugin-core-public.corestart.md @@ -20,7 +20,7 @@ export interface CoreStart | [chrome](./kibana-plugin-core-public.corestart.chrome.md) | ChromeStart | [ChromeStart](./kibana-plugin-core-public.chromestart.md) | | [deprecations](./kibana-plugin-core-public.corestart.deprecations.md) | DeprecationsServiceStart | [DeprecationsServiceStart](./kibana-plugin-core-public.deprecationsservicestart.md) | | [docLinks](./kibana-plugin-core-public.corestart.doclinks.md) | DocLinksStart | [DocLinksStart](./kibana-plugin-core-public.doclinksstart.md) | -| [executionContext](./kibana-plugin-core-public.corestart.executioncontext.md) | ExecutionContextServiceStart | | +| [executionContext](./kibana-plugin-core-public.corestart.executioncontext.md) | ExecutionContextServiceStart | [ExecutionContextServiceStart](./kibana-plugin-core-public.executioncontextservicestart.md) | | [fatalErrors](./kibana-plugin-core-public.corestart.fatalerrors.md) | FatalErrorsStart | [FatalErrorsStart](./kibana-plugin-core-public.fatalerrorsstart.md) | | [http](./kibana-plugin-core-public.corestart.http.md) | HttpStart | [HttpStart](./kibana-plugin-core-public.httpstart.md) | | [i18n](./kibana-plugin-core-public.corestart.i18n.md) | I18nStart | [I18nStart](./kibana-plugin-core-public.i18nstart.md) | diff --git a/docs/development/core/public/kibana-plugin-core-public.executioncontextservicestart.create.md b/docs/development/core/public/kibana-plugin-core-public.executioncontextservicestart.create.md new file mode 100644 index 00000000000000..b36f8ade848e5b --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.executioncontextservicestart.create.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ExecutionContextServiceStart](./kibana-plugin-core-public.executioncontextservicestart.md) > [create](./kibana-plugin-core-public.executioncontextservicestart.create.md) + +## ExecutionContextServiceStart.create property + +Creates a context container carrying the meta-data of a runtime operation. Provided meta-data will be propagated to Kibana and Elasticsearch servers. + +```js +const context = executionContext.create(...); +http.fetch('/endpoint/', { context }); + +``` + +Signature: + +```typescript +create: (context: KibanaExecutionContext) => IExecutionContextContainer; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.executioncontextservicestart.md b/docs/development/core/public/kibana-plugin-core-public.executioncontextservicestart.md new file mode 100644 index 00000000000000..d3eecf601ba9c5 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.executioncontextservicestart.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ExecutionContextServiceStart](./kibana-plugin-core-public.executioncontextservicestart.md) + +## ExecutionContextServiceStart interface + + +Signature: + +```typescript +export interface ExecutionContextServiceStart +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [create](./kibana-plugin-core-public.executioncontextservicestart.create.md) | (context: KibanaExecutionContext) => IExecutionContextContainer | Creates a context container carrying the meta-data of a runtime operation. Provided meta-data will be propagated to Kibana and Elasticsearch servers. +```js +const context = executionContext.create(...); +http.fetch('/endpoint/', { context }); + +``` + | + diff --git a/docs/development/core/public/kibana-plugin-core-public.httpfetchoptions.context.md b/docs/development/core/public/kibana-plugin-core-public.httpfetchoptions.context.md index a0d75b7408ec37..6c6ce3171aaeb3 100644 --- a/docs/development/core/public/kibana-plugin-core-public.httpfetchoptions.context.md +++ b/docs/development/core/public/kibana-plugin-core-public.httpfetchoptions.context.md @@ -7,5 +7,5 @@ Signature: ```typescript -context?: ExecutionContextContainer; +context?: IExecutionContextContainer; ``` diff --git a/docs/development/core/public/kibana-plugin-core-public.httpfetchoptions.md b/docs/development/core/public/kibana-plugin-core-public.httpfetchoptions.md index 6ae819b5a1714a..020a941189013a 100644 --- a/docs/development/core/public/kibana-plugin-core-public.httpfetchoptions.md +++ b/docs/development/core/public/kibana-plugin-core-public.httpfetchoptions.md @@ -18,7 +18,7 @@ export interface HttpFetchOptions extends HttpRequestInit | --- | --- | --- | | [asResponse](./kibana-plugin-core-public.httpfetchoptions.asresponse.md) | boolean | When true the return type of [HttpHandler](./kibana-plugin-core-public.httphandler.md) will be an [HttpResponse](./kibana-plugin-core-public.httpresponse.md) with detailed request and response information. When false, the return type will just be the parsed response body. Defaults to false. | | [asSystemRequest](./kibana-plugin-core-public.httpfetchoptions.assystemrequest.md) | boolean | Whether or not the request should include the "system request" header to differentiate an end user request from Kibana internal request. Can be read on the server-side using KibanaRequest\#isSystemRequest. Defaults to false. | -| [context](./kibana-plugin-core-public.httpfetchoptions.context.md) | ExecutionContextContainer | | +| [context](./kibana-plugin-core-public.httpfetchoptions.context.md) | IExecutionContextContainer | | | [headers](./kibana-plugin-core-public.httpfetchoptions.headers.md) | HttpHeadersInit | Headers to send with the request. See [HttpHeadersInit](./kibana-plugin-core-public.httpheadersinit.md). | | [prependBasePath](./kibana-plugin-core-public.httpfetchoptions.prependbasepath.md) | boolean | Whether or not the request should automatically prepend the basePath. Defaults to true. | | [query](./kibana-plugin-core-public.httpfetchoptions.query.md) | HttpFetchQuery | The query string for an HTTP request. See [HttpFetchQuery](./kibana-plugin-core-public.httpfetchquery.md). | diff --git a/docs/development/core/public/kibana-plugin-core-public.iexecutioncontextcontainer.md b/docs/development/core/public/kibana-plugin-core-public.iexecutioncontextcontainer.md new file mode 100644 index 00000000000000..413b4aaf46b50d --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.iexecutioncontextcontainer.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [IExecutionContextContainer](./kibana-plugin-core-public.iexecutioncontextcontainer.md) + +## IExecutionContextContainer interface + + +Signature: + +```typescript +export interface IExecutionContextContainer +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [toHeader](./kibana-plugin-core-public.iexecutioncontextcontainer.toheader.md) | () => Record<string, string> | | + diff --git a/docs/development/core/public/kibana-plugin-core-public.iexecutioncontextcontainer.toheader.md b/docs/development/core/public/kibana-plugin-core-public.iexecutioncontextcontainer.toheader.md new file mode 100644 index 00000000000000..03132d24bcca5e --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.iexecutioncontextcontainer.toheader.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [IExecutionContextContainer](./kibana-plugin-core-public.iexecutioncontextcontainer.md) > [toHeader](./kibana-plugin-core-public.iexecutioncontextcontainer.toheader.md) + +## IExecutionContextContainer.toHeader property + +Signature: + +```typescript +toHeader: () => Record; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.description.md b/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.description.md new file mode 100644 index 00000000000000..ea8c543c6789e6 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.description.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [KibanaExecutionContext](./kibana-plugin-core-public.kibanaexecutioncontext.md) > [description](./kibana-plugin-core-public.kibanaexecutioncontext.description.md) + +## KibanaExecutionContext.description property + +human readable description. For example, a vis title, action name + +Signature: + +```typescript +readonly description: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.id.md b/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.id.md new file mode 100644 index 00000000000000..1b50d290945852 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.id.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [KibanaExecutionContext](./kibana-plugin-core-public.kibanaexecutioncontext.md) > [id](./kibana-plugin-core-public.kibanaexecutioncontext.id.md) + +## KibanaExecutionContext.id property + +unique value to indentify find the source + +Signature: + +```typescript +readonly id: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.md b/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.md new file mode 100644 index 00000000000000..41724f4914264e --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [KibanaExecutionContext](./kibana-plugin-core-public.kibanaexecutioncontext.md) + +## KibanaExecutionContext interface + + +Signature: + +```typescript +export interface KibanaExecutionContext +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [description](./kibana-plugin-core-public.kibanaexecutioncontext.description.md) | string | human readable description. For example, a vis title, action name | +| [id](./kibana-plugin-core-public.kibanaexecutioncontext.id.md) | string | unique value to indentify find the source | +| [name](./kibana-plugin-core-public.kibanaexecutioncontext.name.md) | string | public name of a user-facing feature | +| [type](./kibana-plugin-core-public.kibanaexecutioncontext.type.md) | string | Kibana application initated an operation. Can be narrowed to an enum later. | +| [url](./kibana-plugin-core-public.kibanaexecutioncontext.url.md) | string | in browser - url to navigate to a current page, on server - endpoint path, for task: task SO url | + diff --git a/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.name.md b/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.name.md new file mode 100644 index 00000000000000..21dde32e21ce71 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.name.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [KibanaExecutionContext](./kibana-plugin-core-public.kibanaexecutioncontext.md) > [name](./kibana-plugin-core-public.kibanaexecutioncontext.name.md) + +## KibanaExecutionContext.name property + +public name of a user-facing feature + +Signature: + +```typescript +readonly name: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.type.md b/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.type.md new file mode 100644 index 00000000000000..ca339ddd9d646a --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.type.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [KibanaExecutionContext](./kibana-plugin-core-public.kibanaexecutioncontext.md) > [type](./kibana-plugin-core-public.kibanaexecutioncontext.type.md) + +## KibanaExecutionContext.type property + +Kibana application initated an operation. Can be narrowed to an enum later. + +Signature: + +```typescript +readonly type: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.url.md b/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.url.md new file mode 100644 index 00000000000000..47ad7604b473d6 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.url.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [KibanaExecutionContext](./kibana-plugin-core-public.kibanaexecutioncontext.md) > [url](./kibana-plugin-core-public.kibanaexecutioncontext.url.md) + +## KibanaExecutionContext.url property + +in browser - url to navigate to a current page, on server - endpoint path, for task: task SO url + +Signature: + +```typescript +readonly url?: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.md b/docs/development/core/public/kibana-plugin-core-public.md index a13438ff48e0b7..d743508e046ea8 100644 --- a/docs/development/core/public/kibana-plugin-core-public.md +++ b/docs/development/core/public/kibana-plugin-core-public.md @@ -63,6 +63,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [DocLinksStart](./kibana-plugin-core-public.doclinksstart.md) | | | [DomainDeprecationDetails](./kibana-plugin-core-public.domaindeprecationdetails.md) | | | [ErrorToastOptions](./kibana-plugin-core-public.errortoastoptions.md) | Options available for [IToasts](./kibana-plugin-core-public.itoasts.md) error APIs. | +| [ExecutionContextServiceStart](./kibana-plugin-core-public.executioncontextservicestart.md) | | | [FatalErrorInfo](./kibana-plugin-core-public.fatalerrorinfo.md) | Represents the message and stack of a fatal Error | | [FatalErrorsSetup](./kibana-plugin-core-public.fatalerrorssetup.md) | FatalErrors stop the Kibana Public Core and displays a fatal error screen with details about the Kibana build and the error. | | [HttpFetchOptions](./kibana-plugin-core-public.httpfetchoptions.md) | All options that may be used with a [HttpHandler](./kibana-plugin-core-public.httphandler.md). | @@ -79,12 +80,14 @@ The plugin integrates with the core system via lifecycle events: `setup` | [I18nStart](./kibana-plugin-core-public.i18nstart.md) | I18nStart.Context is required by any localizable React component from @kbn/i18n and @elastic/eui packages and is supposed to be used as the topmost component for any i18n-compatible React tree. | | [IAnonymousPaths](./kibana-plugin-core-public.ianonymouspaths.md) | APIs for denoting paths as not requiring authentication | | [IBasePath](./kibana-plugin-core-public.ibasepath.md) | APIs for manipulating the basePath on URL segments. | +| [IExecutionContextContainer](./kibana-plugin-core-public.iexecutioncontextcontainer.md) | | | [IExternalUrl](./kibana-plugin-core-public.iexternalurl.md) | APIs for working with external URLs. | | [IExternalUrlPolicy](./kibana-plugin-core-public.iexternalurlpolicy.md) | A policy describing whether access to an external destination is allowed. | | [IHttpFetchError](./kibana-plugin-core-public.ihttpfetcherror.md) | | | [IHttpInterceptController](./kibana-plugin-core-public.ihttpinterceptcontroller.md) | Used to halt a request Promise chain in a [HttpInterceptor](./kibana-plugin-core-public.httpinterceptor.md). | | [IHttpResponseInterceptorOverrides](./kibana-plugin-core-public.ihttpresponseinterceptoroverrides.md) | Properties that can be returned by HttpInterceptor.request to override the response. | | [IUiSettingsClient](./kibana-plugin-core-public.iuisettingsclient.md) | Client-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. [IUiSettingsClient](./kibana-plugin-core-public.iuisettingsclient.md) | +| [KibanaExecutionContext](./kibana-plugin-core-public.kibanaexecutioncontext.md) | | | [NavigateToAppOptions](./kibana-plugin-core-public.navigatetoappoptions.md) | Options for the [navigateToApp API](./kibana-plugin-core-public.applicationstart.navigatetoapp.md) | | [NotificationsSetup](./kibana-plugin-core-public.notificationssetup.md) | | | [NotificationsStart](./kibana-plugin-core-public.notificationsstart.md) | | diff --git a/docs/development/core/server/kibana-plugin-core-server.coresetup.executioncontext.md b/docs/development/core/server/kibana-plugin-core-server.coresetup.executioncontext.md index 473cfe2bbb68ec..847b353aee44f1 100644 --- a/docs/development/core/server/kibana-plugin-core-server.coresetup.executioncontext.md +++ b/docs/development/core/server/kibana-plugin-core-server.coresetup.executioncontext.md @@ -4,6 +4,7 @@ ## CoreSetup.executionContext property +[ExecutionContextSetup](./kibana-plugin-core-server.executioncontextsetup.md) Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.coresetup.md b/docs/development/core/server/kibana-plugin-core-server.coresetup.md index 98d5ec6f36cc90..a66db46adf0f73 100644 --- a/docs/development/core/server/kibana-plugin-core-server.coresetup.md +++ b/docs/development/core/server/kibana-plugin-core-server.coresetup.md @@ -20,7 +20,7 @@ export interface CoreSetupContextSetup | [ContextSetup](./kibana-plugin-core-server.contextsetup.md) | | [deprecations](./kibana-plugin-core-server.coresetup.deprecations.md) | DeprecationsServiceSetup | [DeprecationsServiceSetup](./kibana-plugin-core-server.deprecationsservicesetup.md) | | [elasticsearch](./kibana-plugin-core-server.coresetup.elasticsearch.md) | ElasticsearchServiceSetup | [ElasticsearchServiceSetup](./kibana-plugin-core-server.elasticsearchservicesetup.md) | -| [executionContext](./kibana-plugin-core-server.coresetup.executioncontext.md) | ExecutionContextSetup | | +| [executionContext](./kibana-plugin-core-server.coresetup.executioncontext.md) | ExecutionContextSetup | [ExecutionContextSetup](./kibana-plugin-core-server.executioncontextsetup.md) | | [getStartServices](./kibana-plugin-core-server.coresetup.getstartservices.md) | StartServicesAccessor<TPluginsStart, TStart> | [StartServicesAccessor](./kibana-plugin-core-server.startservicesaccessor.md) | | [http](./kibana-plugin-core-server.coresetup.http.md) | HttpServiceSetup & {
resources: HttpResources;
} | [HttpServiceSetup](./kibana-plugin-core-server.httpservicesetup.md) | | [i18n](./kibana-plugin-core-server.coresetup.i18n.md) | I18nServiceSetup | [I18nServiceSetup](./kibana-plugin-core-server.i18nservicesetup.md) | diff --git a/docs/development/core/server/kibana-plugin-core-server.corestart.executioncontext.md b/docs/development/core/server/kibana-plugin-core-server.corestart.executioncontext.md index f8d50404c8d898..e58f4dc4afa328 100644 --- a/docs/development/core/server/kibana-plugin-core-server.corestart.executioncontext.md +++ b/docs/development/core/server/kibana-plugin-core-server.corestart.executioncontext.md @@ -4,6 +4,7 @@ ## CoreStart.executionContext property +[ExecutionContextStart](./kibana-plugin-core-server.executioncontextstart.md) Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.corestart.md b/docs/development/core/server/kibana-plugin-core-server.corestart.md index 03aeccdb6b44af..d7aaba9149cf5e 100644 --- a/docs/development/core/server/kibana-plugin-core-server.corestart.md +++ b/docs/development/core/server/kibana-plugin-core-server.corestart.md @@ -18,7 +18,7 @@ export interface CoreStart | --- | --- | --- | | [capabilities](./kibana-plugin-core-server.corestart.capabilities.md) | CapabilitiesStart | [CapabilitiesStart](./kibana-plugin-core-server.capabilitiesstart.md) | | [elasticsearch](./kibana-plugin-core-server.corestart.elasticsearch.md) | ElasticsearchServiceStart | [ElasticsearchServiceStart](./kibana-plugin-core-server.elasticsearchservicestart.md) | -| [executionContext](./kibana-plugin-core-server.corestart.executioncontext.md) | ExecutionContextStart | | +| [executionContext](./kibana-plugin-core-server.corestart.executioncontext.md) | ExecutionContextStart | [ExecutionContextStart](./kibana-plugin-core-server.executioncontextstart.md) | | [http](./kibana-plugin-core-server.corestart.http.md) | HttpServiceStart | [HttpServiceStart](./kibana-plugin-core-server.httpservicestart.md) | | [metrics](./kibana-plugin-core-server.corestart.metrics.md) | MetricsServiceStart | [MetricsServiceStart](./kibana-plugin-core-server.metricsservicestart.md) | | [savedObjects](./kibana-plugin-core-server.corestart.savedobjects.md) | SavedObjectsServiceStart | [SavedObjectsServiceStart](./kibana-plugin-core-server.savedobjectsservicestart.md) | diff --git a/docs/development/core/server/kibana-plugin-core-server.executioncontextsetup.get.md b/docs/development/core/server/kibana-plugin-core-server.executioncontextsetup.get.md new file mode 100644 index 00000000000000..d152b9a0c5df29 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.executioncontextsetup.get.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ExecutionContextSetup](./kibana-plugin-core-server.executioncontextsetup.md) > [get](./kibana-plugin-core-server.executioncontextsetup.get.md) + +## ExecutionContextSetup.get() method + +Retrieves an opearation meta-data for the current async context. + +Signature: + +```typescript +get(): IExecutionContextContainer | undefined; +``` +Returns: + +`IExecutionContextContainer | undefined` + diff --git a/docs/development/core/server/kibana-plugin-core-server.executioncontextsetup.md b/docs/development/core/server/kibana-plugin-core-server.executioncontextsetup.md new file mode 100644 index 00000000000000..137df77769c8dc --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.executioncontextsetup.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ExecutionContextSetup](./kibana-plugin-core-server.executioncontextsetup.md) + +## ExecutionContextSetup interface + + +Signature: + +```typescript +export interface ExecutionContextSetup +``` + +## Methods + +| Method | Description | +| --- | --- | +| [get()](./kibana-plugin-core-server.executioncontextsetup.get.md) | Retrieves an opearation meta-data for the current async context. | +| [set(context)](./kibana-plugin-core-server.executioncontextsetup.set.md) | Stores the meta-data of a runtime operation. Data are carried over all async operations automatically. The sequential calls merge provided "context" object shallowly. | + diff --git a/docs/development/core/server/kibana-plugin-core-server.executioncontextsetup.set.md b/docs/development/core/server/kibana-plugin-core-server.executioncontextsetup.set.md new file mode 100644 index 00000000000000..4c8ba4d21b8c43 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.executioncontextsetup.set.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ExecutionContextSetup](./kibana-plugin-core-server.executioncontextsetup.md) > [set](./kibana-plugin-core-server.executioncontextsetup.set.md) + +## ExecutionContextSetup.set() method + +Stores the meta-data of a runtime operation. Data are carried over all async operations automatically. The sequential calls merge provided "context" object shallowly. + +Signature: + +```typescript +set(context: Partial): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| context | Partial<KibanaServerExecutionContext> | | + +Returns: + +`void` + diff --git a/docs/development/core/server/kibana-plugin-core-server.executioncontextstart.md b/docs/development/core/server/kibana-plugin-core-server.executioncontextstart.md new file mode 100644 index 00000000000000..115c09471b3f73 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.executioncontextstart.md @@ -0,0 +1,12 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ExecutionContextStart](./kibana-plugin-core-server.executioncontextstart.md) + +## ExecutionContextStart type + + +Signature: + +```typescript +export declare type ExecutionContextStart = ExecutionContextSetup; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.iexecutioncontextcontainer.md b/docs/development/core/server/kibana-plugin-core-server.iexecutioncontextcontainer.md new file mode 100644 index 00000000000000..2ab3f52b9b5538 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.iexecutioncontextcontainer.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [IExecutionContextContainer](./kibana-plugin-core-server.iexecutioncontextcontainer.md) + +## IExecutionContextContainer interface + + +Signature: + +```typescript +export interface IExecutionContextContainer +``` + +## Methods + +| Method | Description | +| --- | --- | +| [toJSON()](./kibana-plugin-core-server.iexecutioncontextcontainer.tojson.md) | | +| [toString()](./kibana-plugin-core-server.iexecutioncontextcontainer.tostring.md) | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.iexecutioncontextcontainer.tojson.md b/docs/development/core/server/kibana-plugin-core-server.iexecutioncontextcontainer.tojson.md new file mode 100644 index 00000000000000..f67aa88862fee2 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.iexecutioncontextcontainer.tojson.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [IExecutionContextContainer](./kibana-plugin-core-server.iexecutioncontextcontainer.md) > [toJSON](./kibana-plugin-core-server.iexecutioncontextcontainer.tojson.md) + +## IExecutionContextContainer.toJSON() method + +Signature: + +```typescript +toJSON(): Readonly; +``` +Returns: + +`Readonly` + diff --git a/docs/development/core/server/kibana-plugin-core-server.iexecutioncontextcontainer.tostring.md b/docs/development/core/server/kibana-plugin-core-server.iexecutioncontextcontainer.tostring.md new file mode 100644 index 00000000000000..60f9f499cf36c8 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.iexecutioncontextcontainer.tostring.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [IExecutionContextContainer](./kibana-plugin-core-server.iexecutioncontextcontainer.md) > [toString](./kibana-plugin-core-server.iexecutioncontextcontainer.tostring.md) + +## IExecutionContextContainer.toString() method + +Signature: + +```typescript +toString(): string; +``` +Returns: + +`string` + diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.description.md b/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.description.md new file mode 100644 index 00000000000000..00c907b578cf34 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.description.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [KibanaExecutionContext](./kibana-plugin-core-server.kibanaexecutioncontext.md) > [description](./kibana-plugin-core-server.kibanaexecutioncontext.description.md) + +## KibanaExecutionContext.description property + +human readable description. For example, a vis title, action name + +Signature: + +```typescript +readonly description: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.id.md b/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.id.md new file mode 100644 index 00000000000000..d86f621231214e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.id.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [KibanaExecutionContext](./kibana-plugin-core-server.kibanaexecutioncontext.md) > [id](./kibana-plugin-core-server.kibanaexecutioncontext.id.md) + +## KibanaExecutionContext.id property + +unique value to indentify find the source + +Signature: + +```typescript +readonly id: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.md b/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.md new file mode 100644 index 00000000000000..ebc2aeb419a753 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [KibanaExecutionContext](./kibana-plugin-core-server.kibanaexecutioncontext.md) + +## KibanaExecutionContext interface + + +Signature: + +```typescript +export interface KibanaExecutionContext +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [description](./kibana-plugin-core-server.kibanaexecutioncontext.description.md) | string | human readable description. For example, a vis title, action name | +| [id](./kibana-plugin-core-server.kibanaexecutioncontext.id.md) | string | unique value to indentify find the source | +| [name](./kibana-plugin-core-server.kibanaexecutioncontext.name.md) | string | public name of a user-facing feature | +| [type](./kibana-plugin-core-server.kibanaexecutioncontext.type.md) | string | Kibana application initated an operation. Can be narrowed to an enum later. | +| [url](./kibana-plugin-core-server.kibanaexecutioncontext.url.md) | string | in browser - url to navigate to a current page, on server - endpoint path, for task: task SO url | + diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.name.md b/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.name.md new file mode 100644 index 00000000000000..92f58c01bcc112 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.name.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [KibanaExecutionContext](./kibana-plugin-core-server.kibanaexecutioncontext.md) > [name](./kibana-plugin-core-server.kibanaexecutioncontext.name.md) + +## KibanaExecutionContext.name property + +public name of a user-facing feature + +Signature: + +```typescript +readonly name: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.type.md b/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.type.md new file mode 100644 index 00000000000000..534b0cdea17532 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.type.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [KibanaExecutionContext](./kibana-plugin-core-server.kibanaexecutioncontext.md) > [type](./kibana-plugin-core-server.kibanaexecutioncontext.type.md) + +## KibanaExecutionContext.type property + +Kibana application initated an operation. Can be narrowed to an enum later. + +Signature: + +```typescript +readonly type: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.url.md b/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.url.md new file mode 100644 index 00000000000000..dee241cd793986 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.url.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [KibanaExecutionContext](./kibana-plugin-core-server.kibanaexecutioncontext.md) > [url](./kibana-plugin-core-server.kibanaexecutioncontext.url.md) + +## KibanaExecutionContext.url property + +in browser - url to navigate to a current page, on server - endpoint path, for task: task SO url + +Signature: + +```typescript +readonly url?: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanaserverexecutioncontext.md b/docs/development/core/server/kibana-plugin-core-server.kibanaserverexecutioncontext.md new file mode 100644 index 00000000000000..f309e4fd0006c3 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.kibanaserverexecutioncontext.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [KibanaServerExecutionContext](./kibana-plugin-core-server.kibanaserverexecutioncontext.md) + +## KibanaServerExecutionContext interface + + +Signature: + +```typescript +export interface KibanaServerExecutionContext extends Partial +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [requestId](./kibana-plugin-core-server.kibanaserverexecutioncontext.requestid.md) | string | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanaserverexecutioncontext.requestid.md b/docs/development/core/server/kibana-plugin-core-server.kibanaserverexecutioncontext.requestid.md new file mode 100644 index 00000000000000..dff3fd7f2e9ff9 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.kibanaserverexecutioncontext.requestid.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [KibanaServerExecutionContext](./kibana-plugin-core-server.kibanaserverexecutioncontext.md) > [requestId](./kibana-plugin-core-server.kibanaserverexecutioncontext.requestid.md) + +## KibanaServerExecutionContext.requestId property + +Signature: + +```typescript +requestId: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index ac8930c52ac5c3..4a203f10e7cd36 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -77,6 +77,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ElasticsearchServiceStart](./kibana-plugin-core-server.elasticsearchservicestart.md) | | | [ElasticsearchStatusMeta](./kibana-plugin-core-server.elasticsearchstatusmeta.md) | | | [ErrorHttpResponseOptions](./kibana-plugin-core-server.errorhttpresponseoptions.md) | HTTP response parameters | +| [ExecutionContextSetup](./kibana-plugin-core-server.executioncontextsetup.md) | | | [FakeRequest](./kibana-plugin-core-server.fakerequest.md) | Fake request object created manually by Kibana plugins. | | [GetDeprecationsContext](./kibana-plugin-core-server.getdeprecationscontext.md) | | | [GetResponse](./kibana-plugin-core-server.getresponse.md) | | @@ -93,6 +94,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [IContextContainer](./kibana-plugin-core-server.icontextcontainer.md) | An object that handles registration of context providers and configuring handlers with context. | | [ICspConfig](./kibana-plugin-core-server.icspconfig.md) | CSP configuration for use in Kibana. | | [ICustomClusterClient](./kibana-plugin-core-server.icustomclusterclient.md) | See [IClusterClient](./kibana-plugin-core-server.iclusterclient.md) | +| [IExecutionContextContainer](./kibana-plugin-core-server.iexecutioncontextcontainer.md) | | | [IExternalUrlConfig](./kibana-plugin-core-server.iexternalurlconfig.md) | External Url configuration for use in Kibana. | | [IExternalUrlPolicy](./kibana-plugin-core-server.iexternalurlpolicy.md) | A policy describing whether access to an external destination is allowed. | | [IKibanaResponse](./kibana-plugin-core-server.ikibanaresponse.md) | A response data object, expected to returned as a result of [RequestHandler](./kibana-plugin-core-server.requesthandler.md) execution | @@ -103,8 +105,10 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ISavedObjectsPointInTimeFinder](./kibana-plugin-core-server.isavedobjectspointintimefinder.md) | | | [IScopedClusterClient](./kibana-plugin-core-server.iscopedclusterclient.md) | Serves the same purpose as the normal [cluster client](./kibana-plugin-core-server.iclusterclient.md) but exposes an additional asCurrentUser method that doesn't use credentials of the Kibana internal user (as asInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API instead. | | [IUiSettingsClient](./kibana-plugin-core-server.iuisettingsclient.md) | Server-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. | +| [KibanaExecutionContext](./kibana-plugin-core-server.kibanaexecutioncontext.md) | | | [KibanaRequestEvents](./kibana-plugin-core-server.kibanarequestevents.md) | Request events. | | [KibanaRequestRoute](./kibana-plugin-core-server.kibanarequestroute.md) | Request specific route information exposed to a handler. | +| [KibanaServerExecutionContext](./kibana-plugin-core-server.kibanaserverexecutioncontext.md) | | | [LegacyAPICaller](./kibana-plugin-core-server.legacyapicaller.md) | | | [LegacyCallAPIOptions](./kibana-plugin-core-server.legacycallapioptions.md) | The set of options that defines how API call should be made and result be processed. | | [LegacyElasticsearchError](./kibana-plugin-core-server.legacyelasticsearcherror.md) | @deprecated. The new elasticsearch client doesn't wrap errors anymore. 7.16 | @@ -248,6 +252,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [DestructiveRouteMethod](./kibana-plugin-core-server.destructiveroutemethod.md) | Set of HTTP methods changing the state of the server. | | [ElasticsearchClient](./kibana-plugin-core-server.elasticsearchclient.md) | Client used to query the elasticsearch cluster. | | [ElasticsearchClientConfig](./kibana-plugin-core-server.elasticsearchclientconfig.md) | Configuration options to be used to create a [cluster client](./kibana-plugin-core-server.iclusterclient.md) using the [createClient API](./kibana-plugin-core-server.elasticsearchservicestart.createclient.md) | +| [ExecutionContextStart](./kibana-plugin-core-server.executioncontextstart.md) | | | [GetAuthHeaders](./kibana-plugin-core-server.getauthheaders.md) | Get headers to authenticate a user against Elasticsearch. | | [GetAuthState](./kibana-plugin-core-server.getauthstate.md) | Gets authentication state for a request. Returned by auth interceptor. | | [HandlerContextType](./kibana-plugin-core-server.handlercontexttype.md) | Extracts the type of the first argument of a [HandlerFunction](./kibana-plugin-core-server.handlerfunction.md) to represent the type of the context. | diff --git a/src/core/public/execution_context/execution_context_container.ts b/src/core/public/execution_context/execution_context_container.ts index 471fa5dda2925e..489a5de30a33bb 100644 --- a/src/core/public/execution_context/execution_context_container.ts +++ b/src/core/public/execution_context/execution_context_container.ts @@ -23,7 +23,14 @@ function enforceMaxLength(header: string): string { return header.slice(0, MAX_BAGGAGE_LENGTH); } -export class ExecutionContextContainer { +/** + * @public + */ +export interface IExecutionContextContainer { + toHeader: () => Record; +} + +export class ExecutionContextContainer implements IExecutionContextContainer { readonly #context: Readonly; constructor(context: Readonly) { this.#context = context; diff --git a/src/core/public/execution_context/execution_context_service.ts b/src/core/public/execution_context/execution_context_service.ts index f7595e4a345eff..934e68d15be04d 100644 --- a/src/core/public/execution_context/execution_context_service.ts +++ b/src/core/public/execution_context/execution_context_service.ts @@ -6,8 +6,14 @@ * Side Public License, v 1. */ import type { CoreService, KibanaExecutionContext } from '../../types'; -import { ExecutionContextContainer } from './execution_context_container'; +import { + ExecutionContextContainer, + IExecutionContextContainer, +} from './execution_context_container'; +/** + * @public + */ export interface ExecutionContextServiceStart { /** * Creates a context container carrying the meta-data of a runtime operation. @@ -17,7 +23,7 @@ export interface ExecutionContextServiceStart { * http.fetch('/endpoint/', { context }); * ``` */ - create: (context: KibanaExecutionContext) => ExecutionContextContainer; + create: (context: KibanaExecutionContext) => IExecutionContextContainer; } export class ExecutionContextService implements CoreService { diff --git a/src/core/public/execution_context/index.ts b/src/core/public/execution_context/index.ts index efb501b61b5f03..d0c8348d864e79 100644 --- a/src/core/public/execution_context/index.ts +++ b/src/core/public/execution_context/index.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +export type { KibanaExecutionContext } from '../../types'; export { ExecutionContextService } from './execution_context_service'; export type { ExecutionContextServiceStart } from './execution_context_service'; -export type { ExecutionContextContainer } from './execution_context_container'; +export type { IExecutionContextContainer } from './execution_context_container'; diff --git a/src/core/public/http/types.ts b/src/core/public/http/types.ts index e2a69b6832b360..ccf68201bc207a 100644 --- a/src/core/public/http/types.ts +++ b/src/core/public/http/types.ts @@ -8,7 +8,7 @@ import { Observable } from 'rxjs'; import { MaybePromise } from '@kbn/utility-types'; -import type { ExecutionContextContainer } from '../execution_context'; +import type { IExecutionContextContainer } from '../execution_context'; /** @public */ export interface HttpSetup { @@ -272,7 +272,7 @@ export interface HttpFetchOptions extends HttpRequestInit { */ asResponse?: boolean; - context?: ExecutionContextContainer; + context?: IExecutionContextContainer; } /** diff --git a/src/core/public/index.ts b/src/core/public/index.ts index a7fffb5769715a..b3dd3827352bd7 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -186,6 +186,12 @@ export type { export type { DeprecationsServiceStart, ResolveDeprecationResponse } from './deprecations'; +export type { + IExecutionContextContainer, + ExecutionContextServiceStart, + KibanaExecutionContext, +} from './execution_context'; + export type { MountPoint, UnmountCallback, PublicUiSettingsParams } from './types'; export { URL_MAX_LENGTH } from './core_app'; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index ad3364ac93afc6..d23980ff55a2c9 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -432,9 +432,6 @@ export interface CoreStart { deprecations: DeprecationsServiceStart; // (undocumented) docLinks: DocLinksStart; - // Warning: (ae-forgotten-export) The symbol "ExecutionContextServiceStart" needs to be exported by the entry point index.d.ts - // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "ExecutionContextServiceStart" - // // (undocumented) executionContext: ExecutionContextServiceStart; // (undocumented) @@ -719,6 +716,11 @@ export interface ErrorToastOptions extends ToastOptions { toastMessage?: string; } +// @public (undocumented) +export interface ExecutionContextServiceStart { + create: (context: KibanaExecutionContext) => IExecutionContextContainer; +} + // @public export interface FatalErrorInfo { // (undocumented) @@ -757,10 +759,8 @@ export class HttpFetchError extends Error implements IHttpFetchError { export interface HttpFetchOptions extends HttpRequestInit { asResponse?: boolean; asSystemRequest?: boolean; - // Warning: (ae-forgotten-export) The symbol "ExecutionContextContainer" needs to be exported by the entry point index.d.ts - // // (undocumented) - context?: ExecutionContextContainer; + context?: IExecutionContextContainer; headers?: HttpHeadersInit; prependBasePath?: boolean; query?: HttpFetchQuery; @@ -896,6 +896,12 @@ export interface IBasePath { readonly serverBasePath: string; } +// @public (undocumented) +export interface IExecutionContextContainer { + // (undocumented) + toHeader: () => Record; +} + // @public export interface IExternalUrl { validateUrl(relativeOrAbsoluteUrl: string): URL | null; @@ -958,6 +964,15 @@ export interface IUiSettingsClient { set: (key: string, value: any) => Promise; } +// @public (undocumented) +export interface KibanaExecutionContext { + readonly description: string; + readonly id: string; + readonly name: string; + readonly type: string; + readonly url?: string; +} + // @public export type MountPoint = (element: T) => UnmountCallback; diff --git a/src/core/server/elasticsearch/client/cluster_client.ts b/src/core/server/elasticsearch/client/cluster_client.ts index dcbd1d24a7a972..7311ac3737a0fe 100644 --- a/src/core/server/elasticsearch/client/cluster_client.ts +++ b/src/core/server/elasticsearch/client/cluster_client.ts @@ -10,7 +10,7 @@ import { Client } from '@elastic/elasticsearch'; import { Logger } from '../../logging'; import { GetAuthHeaders, Headers, isRealRequest } from '../../http'; import { ensureRawRequest, filterHeaders } from '../../http/router'; -import type { ExecutionContextContainer } from '../../execution_context'; +import type { IExecutionContextContainer } from '../../execution_context'; import { ScopeableRequest } from '../types'; import { ElasticsearchClient } from './types'; import { configureClient } from './configure_client'; @@ -64,7 +64,7 @@ export class ClusterClient implements ICustomClusterClient { logger: Logger, type: string, private readonly getAuthHeaders: GetAuthHeaders = noop, - getExecutionContext: () => ExecutionContextContainer | undefined = noop + getExecutionContext: () => IExecutionContextContainer | undefined = noop ) { this.asInternalUser = configureClient(config, { logger, type, getExecutionContext }); this.rootScopedClient = configureClient(config, { diff --git a/src/core/server/elasticsearch/client/configure_client.ts b/src/core/server/elasticsearch/client/configure_client.ts index d91a61b8d6fa46..4cd853f3e610ed 100644 --- a/src/core/server/elasticsearch/client/configure_client.ts +++ b/src/core/server/elasticsearch/client/configure_client.ts @@ -14,7 +14,7 @@ import type { TransportRequestParams, TransportRequestOptions, } from '@elastic/elasticsearch/lib/Transport'; -import type { ExecutionContextContainer } from '../../execution_context'; +import type { IExecutionContextContainer } from '../../execution_context'; import { Logger } from '../../logging'; import { parseClientOptions, ElasticsearchClientConfig } from './client_config'; @@ -31,7 +31,7 @@ export const configureClient = ( logger: Logger; type: string; scoped?: boolean; - getExecutionContext?: () => ExecutionContextContainer | undefined; + getExecutionContext?: () => IExecutionContextContainer | undefined; } ): Client => { const clientOptions = parseClientOptions(config, scoped); diff --git a/src/core/server/execution_context/execution_context_container.ts b/src/core/server/execution_context/execution_context_container.ts index 43b89a6ac4ff38..c2c27b2f8c0d5d 100644 --- a/src/core/server/execution_context/execution_context_container.ts +++ b/src/core/server/execution_context/execution_context_container.ts @@ -28,7 +28,15 @@ function parseHeader(header?: string): KibanaExecutionContext | undefined { } } -export class ExecutionContextContainer { +/** + * @public + */ +export interface IExecutionContextContainer { + toString(): string; + toJSON(): Readonly; +} + +export class ExecutionContextContainer implements IExecutionContextContainer { readonly #context: Readonly; constructor(context: Readonly) { this.#context = context; diff --git a/src/core/server/execution_context/execution_context_service.ts b/src/core/server/execution_context/execution_context_service.ts index 287aedb6d56075..0c6e02ff2e1eee 100644 --- a/src/core/server/execution_context/execution_context_service.ts +++ b/src/core/server/execution_context/execution_context_service.ts @@ -11,7 +11,11 @@ import type { CoreService, KibanaExecutionContext } from '../../types'; import type { CoreContext } from '../core_context'; import type { Logger } from '../logging'; -import { ExecutionContextContainer, getParentContextFrom } from './execution_context_container'; +import { + ExecutionContextContainer, + IExecutionContextContainer, + getParentContextFrom, +} from './execution_context_container'; /** * @public @@ -27,7 +31,7 @@ export interface IExecutionContext { getParentContextFrom(headers: Record): KibanaExecutionContext | undefined; set(context: Partial): void; reset(): void; - get(): ExecutionContextContainer | undefined; + get(): IExecutionContextContainer | undefined; } /** @@ -53,7 +57,7 @@ export interface ExecutionContextSetup { /** * Retrieves an opearation meta-data for the current async context. **/ - get(): ExecutionContextContainer | undefined; + get(): IExecutionContextContainer | undefined; } /** @@ -64,11 +68,11 @@ export type ExecutionContextStart = ExecutionContextSetup; export class ExecutionContextService implements CoreService { private readonly log: Logger; - private readonly asyncLocalStorage: AsyncLocalStorage; + private readonly asyncLocalStorage: AsyncLocalStorage; constructor(coreContext: CoreContext) { this.log = coreContext.logger.get('execution_context'); - this.asyncLocalStorage = new AsyncLocalStorage(); + this.asyncLocalStorage = new AsyncLocalStorage(); } setup(): InternalExecutionContextSetup { @@ -105,7 +109,7 @@ export class ExecutionContextService this.asyncLocalStorage.enterWith(undefined); } - private get(): ExecutionContextContainer | undefined { + private get(): IExecutionContextContainer | undefined { return this.asyncLocalStorage.getStore(); } } diff --git a/src/core/server/execution_context/index.ts b/src/core/server/execution_context/index.ts index 1f378e933817f0..4fdc151b9aa1d3 100644 --- a/src/core/server/execution_context/index.ts +++ b/src/core/server/execution_context/index.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +export type { KibanaExecutionContext } from '../../types'; export { ExecutionContextService } from './execution_context_service'; export type { InternalExecutionContextSetup, @@ -13,5 +14,6 @@ export type { ExecutionContextSetup, ExecutionContextStart, IExecutionContext, + KibanaServerExecutionContext, } from './execution_context_service'; -export type { ExecutionContextContainer } from './execution_context_container'; +export type { IExecutionContextContainer } from './execution_context_container'; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 47af421882b1a9..d2a4b4bff33900 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -69,8 +69,6 @@ import { CoreServicesUsageData, } from './core_usage_data'; -import type { ExecutionContextSetup, ExecutionContextStart } from './execution_context'; - export type { CoreUsageStats, CoreUsageData, @@ -80,6 +78,16 @@ export type { ConfigUsageData, }; +import type { ExecutionContextSetup, ExecutionContextStart } from './execution_context'; + +export type { + ExecutionContextSetup, + ExecutionContextStart, + IExecutionContextContainer, + KibanaServerExecutionContext, + KibanaExecutionContext, +} from './execution_context'; + export { bootstrap } from './bootstrap'; export type { Capabilities, diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index f659a26af0580b..ed55c6e3d09cbe 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -522,9 +522,6 @@ export interface CoreSetup): void; +} + +// @public (undocumented) +export type ExecutionContextStart = ExecutionContextSetup; + // @public export interface FakeRequest { headers: Headers; @@ -1197,6 +1200,14 @@ export interface ICustomClusterClient extends IClusterClient { close: () => Promise; } +// @public (undocumented) +export interface IExecutionContextContainer { + // (undocumented) + toJSON(): Readonly; + // (undocumented) + toString(): string; +} + // @public export interface IExternalUrlConfig { readonly policy: IExternalUrlPolicy[]; @@ -1313,6 +1324,15 @@ export interface IUiSettingsClient { setMany: (changes: Record) => Promise; } +// @public (undocumented) +export interface KibanaExecutionContext { + readonly description: string; + readonly id: string; + readonly name: string; + readonly type: string; + readonly url?: string; +} + // @public export class KibanaRequest { // @internal (undocumented) @@ -1384,6 +1404,12 @@ export const kibanaResponseFactory: { noContent: (options?: HttpResponseOptions) => KibanaResponse; }; +// @public (undocumented) +export interface KibanaServerExecutionContext extends Partial { + // (undocumented) + requestId: string; +} + // Warning: (ae-forgotten-export) The symbol "KnownKeys" needs to be exported by the entry point index.d.ts // // @public diff --git a/src/core/types/execution_context.ts b/src/core/types/execution_context.ts index 18066af336990c..06996ddd83418a 100644 --- a/src/core/types/execution_context.ts +++ b/src/core/types/execution_context.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -/** @internal */ +/** @public */ export interface KibanaExecutionContext { /** * Kibana application initated an operation. From 364efa33d8c7eb98435d668c54f486e15511208a Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Tue, 6 Jul 2021 09:39:32 +0200 Subject: [PATCH 28/36] Apply suggestions from code review Co-authored-by: Josh Dover <1813008+joshdover@users.noreply.github.com> --- src/core/types/execution_context.ts | 2 +- .../test_suites/core_plugins/execution_context.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/types/execution_context.ts b/src/core/types/execution_context.ts index 06996ddd83418a..e624ea82f22fc7 100644 --- a/src/core/types/execution_context.ts +++ b/src/core/types/execution_context.ts @@ -15,7 +15,7 @@ export interface KibanaExecutionContext { readonly type: string; // 'visualization' | 'actions' | 'server' | ..; /** public name of a user-facing feature */ readonly name: string; // 'TSVB' | 'Lens' | 'action_execution' | ..; - /** unique value to indentify find the source */ + /** unique value to identify the source */ readonly id: string; /** human readable description. For example, a vis title, action name */ readonly description: string; diff --git a/test/plugin_functional/test_suites/core_plugins/execution_context.ts b/test/plugin_functional/test_suites/core_plugins/execution_context.ts index 2f80423e47739a..51312872678f20 100644 --- a/test/plugin_functional/test_suites/core_plugins/execution_context.ts +++ b/test/plugin_functional/test_suites/core_plugins/execution_context.ts @@ -29,7 +29,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide type: 'execution_context_app', name: 'Execution context app', id: '42', - // add a non-ASCII symbols to make it doesn't break the context propagation mechanism + // add a non-ASCII symbols to make sure it doesn't break the context propagation mechanism description: 'какое-то странное описание', }); From 458a3517380111dc03d9ecb946f9373eca5205b3 Mon Sep 17 00:00:00 2001 From: restrry Date: Tue, 6 Jul 2021 11:55:34 +0300 Subject: [PATCH 29/36] address comments --- .../execution_context_service.mock.ts | 5 +- src/core/public/http/fetch.test.ts | 14 +++++ .../client/cluster_client.test.ts | 8 +-- .../integration_tests/tracing.test.ts | 53 ++++++++++++++++++- 4 files changed, 73 insertions(+), 7 deletions(-) diff --git a/src/core/public/execution_context/execution_context_service.mock.ts b/src/core/public/execution_context/execution_context_service.mock.ts index 839beaed7c1fed..d8148b0af807dc 100644 --- a/src/core/public/execution_context/execution_context_service.mock.ts +++ b/src/core/public/execution_context/execution_context_service.mock.ts @@ -10,7 +10,7 @@ import type { Plugin } from 'src/core/public'; import type { ExecutionContextServiceStart } from './execution_context_service'; import type { ExecutionContextContainer } from './execution_context_container'; -const creteContainerMock = () => { +const createContainerMock = () => { const mock: jest.Mocked> = { toHeader: jest.fn(), }; @@ -18,7 +18,7 @@ const creteContainerMock = () => { }; const createStartContractMock = () => { const mock: jest.Mocked = { - create: jest.fn().mockReturnValue(creteContainerMock()), + create: jest.fn().mockReturnValue(createContainerMock()), }; return mock; }; @@ -32,4 +32,5 @@ const createMock = (): jest.Mocked => ({ export const executionContextServiceMock = { create: createMock, createStartContract: createStartContractMock, + createContainer: createContainerMock, }; diff --git a/src/core/public/http/fetch.test.ts b/src/core/public/http/fetch.test.ts index 1208df032ff6f9..67ec816d084307 100644 --- a/src/core/public/http/fetch.test.ts +++ b/src/core/public/http/fetch.test.ts @@ -15,6 +15,7 @@ import { first } from 'rxjs/operators'; import { Fetch } from './fetch'; import { BasePath } from './base_path'; import { HttpResponse, HttpFetchOptionsWithPath } from './types'; +import { executionContextServiceMock } from '../execution_context/execution_context_service.mock'; function delay(duration: number) { return new Promise((r) => setTimeout(r, duration)); @@ -227,6 +228,19 @@ describe('Fetch', () => { ); }); + it('should inject context headers if provided', async () => { + fetchMock.get('*', {}); + const executionContainerMock = executionContextServiceMock.createContainer(); + executionContainerMock.toHeader.mockReturnValueOnce({ 'x-kbn-context': 'value' }); + await fetchInstance.fetch('/my/path', { + context: executionContainerMock, + }); + + expect(fetchMock.lastOptions()!.headers).toMatchObject({ + 'x-kbn-context': 'value', + }); + }); + // Deprecated header used by legacy platform pre-7.7. Remove in 8.x. it('should not allow overwriting of kbn-system-api when asSystemRequest: true', async () => { fetchMock.get('*', {}); diff --git a/src/core/server/elasticsearch/client/cluster_client.test.ts b/src/core/server/elasticsearch/client/cluster_client.test.ts index 33a4aec6212da5..dc43969ca03d05 100644 --- a/src/core/server/elasticsearch/client/cluster_client.test.ts +++ b/src/core/server/elasticsearch/client/cluster_client.test.ts @@ -29,6 +29,8 @@ const createConfig = ( }; }; +const getExecutionContextMock = jest.fn(); + describe('ClusterClient', () => { let logger: ReturnType; let getAuthHeaders: jest.MockedFunction; @@ -56,18 +58,18 @@ describe('ClusterClient', () => { it('creates a single internal and scoped client during initialization', () => { const config = createConfig(); - new ClusterClient(config, logger, 'custom-type', getAuthHeaders); + new ClusterClient(config, logger, 'custom-type', getAuthHeaders, getExecutionContextMock); expect(configureClientMock).toHaveBeenCalledTimes(2); expect(configureClientMock).toHaveBeenCalledWith(config, { logger, type: 'custom-type', - getExecutionContext: expect.any(Function), + getExecutionContext: getExecutionContextMock, }); expect(configureClientMock).toHaveBeenCalledWith(config, { logger, type: 'custom-type', - getExecutionContext: expect.any(Function), + getExecutionContext: getExecutionContextMock, scoped: true, }); }); diff --git a/src/core/server/execution_context/integration_tests/tracing.test.ts b/src/core/server/execution_context/integration_tests/tracing.test.ts index b6013c18c37d7e..c3334a247c2101 100644 --- a/src/core/server/execution_context/integration_tests/tracing.test.ts +++ b/src/core/server/execution_context/integration_tests/tracing.test.ts @@ -128,7 +128,7 @@ describe('trace', () => { expect(header).toEqual(expect.any(String)); }); - it('can be rewritten during Elasticsearch client call', async () => { + it('can be overriden during Elasticsearch client call', async () => { const { http } = await root.setup(); const { createRouter } = http; @@ -155,8 +155,24 @@ describe('trace', () => { expect(header).toBe('new-opaque-id'); }); }); + describe('execution context', () => { - it('sets execution context for a request handler', async () => { + it('sets execution context for a sync request handler', async () => { + const { executionContext, http } = await root.setup(); + const { createRouter } = http; + + const router = createRouter(''); + router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { + executionContext.set(parentContext); + return res.ok({ body: executionContext.get() }); + }); + + await root.start(); + const response = await kbnTestServer.request.get(root, '/execution-context').expect(200); + expect(response.body).toEqual({ ...parentContext, requestId: expect.any(String) }); + }); + + it('sets execution context for an async request handler', async () => { const { executionContext, http } = await root.setup(); const { createRouter } = http; @@ -223,6 +239,39 @@ describe('trace', () => { expect(bodyA.requestId).not.toBe(bodyC.requestId); }); + it('execution context is uniq for concurrent requests when "x-opaque-id" provided', async () => { + const { executionContext, http } = await root.setup(); + const { createRouter } = http; + + const router = createRouter(''); + let id = 2; + router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { + executionContext.set(parentContext); + await delay(id-- * 100); + return res.ok({ body: executionContext.get() }); + }); + + await root.start(); + const responseA = kbnTestServer.request + .get(root, '/execution-context') + .set('x-opaque-id', 'req-1'); + const responseB = kbnTestServer.request + .get(root, '/execution-context') + .set('x-opaque-id', 'req-2'); + const responseC = kbnTestServer.request + .get(root, '/execution-context') + .set('x-opaque-id', 'req-3'); + + const [{ body: bodyA }, { body: bodyB }, { body: bodyC }] = await Promise.all([ + responseA, + responseB, + responseC, + ]); + expect(bodyA.requestId).toBe('req-1'); + expect(bodyB.requestId).toBe('req-2'); + expect(bodyC.requestId).toBe('req-3'); + }); + it('parses the parent context if present', async () => { const { executionContext, http } = await root.setup(); const { createRouter } = http; From 168cc22dfc6ce7b8f331ebb8e73a0e28989a15b2 Mon Sep 17 00:00:00 2001 From: restrry Date: Tue, 6 Jul 2021 15:52:18 +0300 Subject: [PATCH 30/36] remove execution context cleanup --- src/core/server/http/http_server.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index 9d98b64987fad8..85c035154a7a73 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -152,8 +152,6 @@ export class HttpServer { this.setupConditionalCompression(config); this.setupResponseLogging(); this.setupGracefulShutdownHandlers(); - // cleanup context is the last operation to keep the context for the internal on('response' handlers. - this.setupContextExecutionCleanup(executionContext); return { registerRouter: this.registerRouter.bind(this), @@ -339,7 +337,10 @@ export class HttpServer { const requestId = getRequestId(request, config.requestId); const parentContext = executionContext?.getParentContextFrom(request.headers); - executionContext?.set({ ...parentContext, requestId }); + executionContext?.set({ + ...parentContext, + requestId, + }); request.app = { ...(request.app ?? {}), @@ -350,13 +351,6 @@ export class HttpServer { }); } - private setupContextExecutionCleanup(executionContext?: InternalExecutionContextSetup) { - if (!executionContext) return; - this.server!.events.on('response', function () { - executionContext.reset(); - }); - } - private registerOnPreAuth(fn: OnPreAuthHandler) { if (this.server === undefined) { throw new Error('Server is not created yet'); From 7013cb6ce4151e0f71a381e47a08d5ef2c0beae3 Mon Sep 17 00:00:00 2001 From: restrry Date: Tue, 6 Jul 2021 22:27:03 +0300 Subject: [PATCH 31/36] add option to disable execution_context service on the server side --- .../elasticsearch/client/cluster_client.ts | 11 +++- .../elasticsearch/client/configure_client.ts | 1 + .../execution_context_config.ts | 24 +++++++ .../execution_context_service.test.ts | 47 +++++++++++++ .../execution_context_service.ts | 24 ++++++- src/core/server/execution_context/index.ts | 1 + .../integration_tests/tracing.test.ts | 66 +++++++++++++++++++ src/core/server/server.ts | 2 + 8 files changed, 171 insertions(+), 5 deletions(-) create mode 100644 src/core/server/execution_context/execution_context_config.ts diff --git a/src/core/server/elasticsearch/client/cluster_client.ts b/src/core/server/elasticsearch/client/cluster_client.ts index 7311ac3737a0fe..d164736cead071 100644 --- a/src/core/server/elasticsearch/client/cluster_client.ts +++ b/src/core/server/elasticsearch/client/cluster_client.ts @@ -8,7 +8,7 @@ import { Client } from '@elastic/elasticsearch'; import { Logger } from '../../logging'; -import { GetAuthHeaders, Headers, isRealRequest } from '../../http'; +import { GetAuthHeaders, Headers, isKibanaRequest, isRealRequest } from '../../http'; import { ensureRawRequest, filterHeaders } from '../../http/router'; import type { IExecutionContextContainer } from '../../execution_context'; import { ScopeableRequest } from '../types'; @@ -73,7 +73,8 @@ export class ClusterClient implements ICustomClusterClient { getExecutionContext, scoped: true, }); - this.allowListHeaders = this.config.requestHeadersWhitelist; + + this.allowListHeaders = ['x-opaque-id', ...this.config.requestHeadersWhitelist]; } asScoped(request: ScopeableRequest) { @@ -96,9 +97,13 @@ export class ClusterClient implements ICustomClusterClient { let scopedHeaders: Headers; if (isRealRequest(request)) { const requestHeaders = ensureRawRequest(request).headers; + const requestIdHeaders = isKibanaRequest(request) ? { 'x-opaque-id': request.id } : {}; const authHeaders = this.getAuthHeaders(request); - scopedHeaders = filterHeaders({ ...requestHeaders, ...authHeaders }, this.allowListHeaders); + scopedHeaders = filterHeaders( + { ...requestHeaders, ...requestIdHeaders, ...authHeaders }, + this.allowListHeaders + ); } else { scopedHeaders = filterHeaders(request?.headers ?? {}, this.config.requestHeadersWhitelist); } diff --git a/src/core/server/elasticsearch/client/configure_client.ts b/src/core/server/elasticsearch/client/configure_client.ts index 4cd853f3e610ed..ce4bd6fa2c59b4 100644 --- a/src/core/server/elasticsearch/client/configure_client.ts +++ b/src/core/server/elasticsearch/client/configure_client.ts @@ -40,6 +40,7 @@ export const configureClient = ( const opts = options || {}; const opaqueId = getExecutionContext()?.toString(); if (opaqueId && !opts.opaqueId) { + // rewrites headers['x-opaque-id'] if it presents opts.opaqueId = opaqueId; } return super.request(params, opts); diff --git a/src/core/server/execution_context/execution_context_config.ts b/src/core/server/execution_context/execution_context_config.ts new file mode 100644 index 00000000000000..af6e7253433f7b --- /dev/null +++ b/src/core/server/execution_context/execution_context_config.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ + +import { TypeOf, schema } from '@kbn/config-schema'; +import { ServiceConfigDescriptor } from '../internal_types'; + +const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: true }), +}); + +/** + * @internal + */ +export type ExecutionContextConfigType = TypeOf; + +export const config: ServiceConfigDescriptor = { + path: 'execution_context', + schema: configSchema, +}; diff --git a/src/core/server/execution_context/execution_context_service.test.ts b/src/core/server/execution_context/execution_context_service.test.ts index b1cc9fc6f8bf4b..9b9ab0f48bacb0 100644 --- a/src/core/server/execution_context/execution_context_service.test.ts +++ b/src/core/server/execution_context/execution_context_service.test.ts @@ -5,6 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ +import { BehaviorSubject } from 'rxjs'; import { ExecutionContextService, InternalExecutionContextSetup, @@ -16,6 +17,7 @@ describe('ExecutionContextService', () => { describe('setup', () => { let service: InternalExecutionContextSetup; const core = mockCoreContext.create(); + core.configService.atPath.mockReturnValue(new BehaviorSubject({ enabled: true })); beforeEach(() => { service = new ExecutionContextService(core).setup(); }); @@ -81,4 +83,49 @@ describe('ExecutionContextService', () => { ]); }); }); + + describe('config', () => { + it('can be disabled', async () => { + const core = mockCoreContext.create(); + core.configService.atPath.mockReturnValue(new BehaviorSubject({ enabled: false })); + const service = new ExecutionContextService(core).setup(); + const chainA = await Promise.resolve().then(async () => { + service.set({ + requestId: '0000', + }); + await delay(100); + return service.get(); + }); + + expect(chainA).toBeUndefined(); + }); + + it('reacts to config changes', async () => { + const core = mockCoreContext.create(); + const config$ = new BehaviorSubject({ enabled: false }); + core.configService.atPath.mockReturnValue(config$); + const service = new ExecutionContextService(core).setup(); + function exec() { + return Promise.resolve().then(async () => { + service.set({ + requestId: '0000', + }); + await delay(100); + return service.get(); + }); + } + expect(await exec()).toBeUndefined(); + + config$.next({ + enabled: true, + }); + expect(await exec()).toBeDefined(); + + config$.next({ + enabled: false, + }); + + expect(await exec()).toBeUndefined(); + }); + }); }); diff --git a/src/core/server/execution_context/execution_context_service.ts b/src/core/server/execution_context/execution_context_service.ts index 0c6e02ff2e1eee..95a854f84d145d 100644 --- a/src/core/server/execution_context/execution_context_service.ts +++ b/src/core/server/execution_context/execution_context_service.ts @@ -6,10 +6,12 @@ * Side Public License, v 1. */ import { AsyncLocalStorage } from 'async_hooks'; +import type { Subscription } from 'rxjs'; import type { CoreService, KibanaExecutionContext } from '../../types'; import type { CoreContext } from '../core_context'; import type { Logger } from '../logging'; +import type { ExecutionContextConfigType } from './execution_context_config'; import { ExecutionContextContainer, @@ -69,13 +71,21 @@ export class ExecutionContextService implements CoreService { private readonly log: Logger; private readonly asyncLocalStorage: AsyncLocalStorage; + private enabled = false; + private configSubscription?: Subscription; - constructor(coreContext: CoreContext) { + constructor(private readonly coreContext: CoreContext) { this.log = coreContext.logger.get('execution_context'); this.asyncLocalStorage = new AsyncLocalStorage(); } setup(): InternalExecutionContextSetup { + this.configSubscription = this.coreContext.configService + .atPath('execution_context') + .subscribe((config) => { + this.enabled = config.enabled; + }); + return { getParentContextFrom, set: this.set.bind(this), @@ -92,9 +102,17 @@ export class ExecutionContextService get: this.get.bind(this), }; } - stop() {} + + stop() { + this.enabled = false; + if (this.configSubscription) { + this.configSubscription.unsubscribe(); + this.configSubscription = undefined; + } + } private set(context: KibanaServerExecutionContext) { + if (!this.enabled) return; const prevValue = this.asyncLocalStorage.getStore(); // merges context objects shallowly. repeats the deafult logic of apm.setCustomContext(ctx) const contextContainer = new ExecutionContextContainer({ ...prevValue?.toJSON(), ...context }); @@ -105,11 +123,13 @@ export class ExecutionContextService } private reset() { + if (!this.enabled) return; // @ts-expect-error "undefined" is not supported in type definitions, which is wrong this.asyncLocalStorage.enterWith(undefined); } private get(): IExecutionContextContainer | undefined { + if (!this.enabled) return; return this.asyncLocalStorage.getStore(); } } diff --git a/src/core/server/execution_context/index.ts b/src/core/server/execution_context/index.ts index 4fdc151b9aa1d3..f8018c75995e7f 100644 --- a/src/core/server/execution_context/index.ts +++ b/src/core/server/execution_context/index.ts @@ -17,3 +17,4 @@ export type { KibanaServerExecutionContext, } from './execution_context_service'; export type { IExecutionContextContainer } from './execution_context_container'; +export { config } from './execution_context_config'; diff --git a/src/core/server/execution_context/integration_tests/tracing.test.ts b/src/core/server/execution_context/integration_tests/tracing.test.ts index c3334a247c2101..36e40fe5ee973c 100644 --- a/src/core/server/execution_context/integration_tests/tracing.test.ts +++ b/src/core/server/execution_context/integration_tests/tracing.test.ts @@ -154,6 +154,72 @@ describe('trace', () => { const header = response.body['x-opaque-id']; expect(header).toBe('new-opaque-id'); }); + + it('passed to Elasticsearch scoped client calls even if ExecutionContext Serivce is disabled', async () => { + const kibana = kbnTestServer.createRootWithCorePlugins({ + execution_context: { enabled: false }, + plugins: { initialize: false }, + server: { + requestId: { + allowFromAnyIp: true, + }, + }, + }); + const { http } = await kibana.setup(); + const { createRouter } = http; + + const router = createRouter(''); + router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { + const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping(); + return res.ok({ body: headers || {} }); + }); + + await kibana.start(); + + const myOpaqueId = 'my-opaque-id'; + const response = await kbnTestServer.request + .get(kibana, '/execution-context') + .set('x-opaque-id', myOpaqueId) + .expect(200); + + const header = response.body['x-opaque-id']; + expect(header).toBe(myOpaqueId); + }); + + it('does not pass context if ExecutionContext Serivce is disabled', async () => { + const kibana = kbnTestServer.createRootWithCorePlugins({ + execution_context: { enabled: false }, + plugins: { initialize: false }, + server: { + requestId: { + allowFromAnyIp: true, + }, + }, + }); + const { http, executionContext } = await kibana.setup(); + const { createRouter } = http; + + const router = createRouter(''); + router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { + executionContext.set(parentContext); + const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping(); + return res.ok({ + body: { context: executionContext.get()?.toJSON(), header: headers?.['x-opaque-id'] }, + }); + }); + + await kibana.start(); + + const myOpaqueId = 'my-opaque-id'; + const response = await kbnTestServer.request + .get(kibana, '/execution-context') + .set('x-opaque-id', myOpaqueId) + .expect(200); + + expect(response.body).toEqual({ + header: 'my-opaque-id', + }); + }); }); describe('execution context', () => { diff --git a/src/core/server/server.ts b/src/core/server/server.ts index a665d99bab568c..5a75550280a968 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -49,6 +49,7 @@ import { CoreUsageDataService } from './core_usage_data'; import { DeprecationsService } from './deprecations'; import { CoreRouteHandlerContext } from './core_route_handler_context'; import { config as externalUrlConfig } from './external_url'; +import { config as executionContextConfig } from './execution_context'; const coreId = Symbol('core'); const rootConfigPath = ''; @@ -306,6 +307,7 @@ export class Server { public setupCoreConfig() { const configDescriptors: Array> = [ + executionContextConfig, pathConfig, cspConfig, elasticsearchConfig, From 12c1953cdd965ab863fc7690ee3871f97056daf8 Mon Sep 17 00:00:00 2001 From: restrry Date: Tue, 6 Jul 2021 22:42:04 +0300 Subject: [PATCH 32/36] put x-opaque-id test back --- .../client/cluster_client.test.ts | 56 ++++++++++++++++++- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/src/core/server/elasticsearch/client/cluster_client.test.ts b/src/core/server/elasticsearch/client/cluster_client.test.ts index dc43969ca03d05..f04d34bcc0f62c 100644 --- a/src/core/server/elasticsearch/client/cluster_client.test.ts +++ b/src/core/server/elasticsearch/client/cluster_client.test.ts @@ -142,7 +142,7 @@ describe('ClusterClient', () => { expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith({ - headers: { ...DEFAULT_HEADERS, foo: 'bar' }, + headers: { ...DEFAULT_HEADERS, foo: 'bar', 'x-opaque-id': expect.any(String) }, }); }); @@ -162,7 +162,7 @@ describe('ClusterClient', () => { expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith({ - headers: { ...DEFAULT_HEADERS, authorization: 'auth' }, + headers: { ...DEFAULT_HEADERS, authorization: 'auth', 'x-opaque-id': expect.any(String) }, }); }); @@ -186,7 +186,7 @@ describe('ClusterClient', () => { expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith({ - headers: { ...DEFAULT_HEADERS, authorization: 'auth' }, + headers: { ...DEFAULT_HEADERS, authorization: 'auth', 'x-opaque-id': expect.any(String) }, }); }); @@ -211,6 +211,27 @@ describe('ClusterClient', () => { ...DEFAULT_HEADERS, foo: 'bar', hello: 'dolly', + 'x-opaque-id': expect.any(String), + }, + }); + }); + + it('adds the x-opaque-id header based on the request id', () => { + const config = createConfig(); + getAuthHeaders.mockReturnValue({}); + + const clusterClient = new ClusterClient(config, logger, 'custom-type', getAuthHeaders); + const request = httpServerMock.createKibanaRequest({ + kibanaRequestState: { requestId: 'my-fake-id', requestUuid: 'ignore-this-id' }, + }); + + clusterClient.asScoped(request); + + expect(scopedClient.child).toHaveBeenCalledTimes(1); + expect(scopedClient.child).toHaveBeenCalledWith({ + headers: { + ...DEFAULT_HEADERS, + 'x-opaque-id': 'my-fake-id', }, }); }); @@ -238,6 +259,7 @@ describe('ClusterClient', () => { ...DEFAULT_HEADERS, foo: 'auth', hello: 'dolly', + 'x-opaque-id': expect.any(String), }, }); }); @@ -265,6 +287,32 @@ describe('ClusterClient', () => { ...DEFAULT_HEADERS, foo: 'request', hello: 'dolly', + 'x-opaque-id': expect.any(String), + }, + }); + }); + + it('respect the precedence of x-opaque-id header over config headers', () => { + const config = createConfig({ + customHeaders: { + 'x-opaque-id': 'from config', + }, + }); + getAuthHeaders.mockReturnValue({}); + + const clusterClient = new ClusterClient(config, logger, 'custom-type', getAuthHeaders); + const request = httpServerMock.createKibanaRequest({ + headers: { foo: 'request' }, + kibanaRequestState: { requestId: 'from request', requestUuid: 'ignore-this-id' }, + }); + + clusterClient.asScoped(request); + + expect(scopedClient.child).toHaveBeenCalledTimes(1); + expect(scopedClient.child).toHaveBeenCalledWith({ + headers: { + ...DEFAULT_HEADERS, + 'x-opaque-id': 'from request', }, }); }); @@ -287,6 +335,7 @@ describe('ClusterClient', () => { expect(scopedClient.child).toHaveBeenCalledWith({ headers: { [headerKey]: 'foo', + 'x-opaque-id': expect.any(String), }, }); }); @@ -309,6 +358,7 @@ describe('ClusterClient', () => { expect(scopedClient.child).toHaveBeenCalledWith({ headers: { [headerKey]: 'foo', + 'x-opaque-id': expect.any(String), }, }); }); From 2bf356710fea4a1bf8657c5ba29cfa1a7640a638 Mon Sep 17 00:00:00 2001 From: restrry Date: Tue, 6 Jul 2021 22:47:03 +0300 Subject: [PATCH 33/36] put tests back --- .../client/cluster_client.test.ts | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/core/server/elasticsearch/client/cluster_client.test.ts b/src/core/server/elasticsearch/client/cluster_client.test.ts index f04d34bcc0f62c..f96f39349887ea 100644 --- a/src/core/server/elasticsearch/client/cluster_client.test.ts +++ b/src/core/server/elasticsearch/client/cluster_client.test.ts @@ -29,8 +29,6 @@ const createConfig = ( }; }; -const getExecutionContextMock = jest.fn(); - describe('ClusterClient', () => { let logger: ReturnType; let getAuthHeaders: jest.MockedFunction; @@ -57,7 +55,7 @@ describe('ClusterClient', () => { it('creates a single internal and scoped client during initialization', () => { const config = createConfig(); - + const getExecutionContextMock = jest.fn(); new ClusterClient(config, logger, 'custom-type', getAuthHeaders, getExecutionContextMock); expect(configureClientMock).toHaveBeenCalledTimes(2); @@ -292,42 +290,40 @@ describe('ClusterClient', () => { }); }); - it('respect the precedence of x-opaque-id header over config headers', () => { + it('respect the precedence of config headers over default headers', () => { + const headerKey = Object.keys(DEFAULT_HEADERS)[0]; const config = createConfig({ customHeaders: { - 'x-opaque-id': 'from config', + [headerKey]: 'foo', }, }); getAuthHeaders.mockReturnValue({}); const clusterClient = new ClusterClient(config, logger, 'custom-type', getAuthHeaders); - const request = httpServerMock.createKibanaRequest({ - headers: { foo: 'request' }, - kibanaRequestState: { requestId: 'from request', requestUuid: 'ignore-this-id' }, - }); + const request = httpServerMock.createKibanaRequest(); clusterClient.asScoped(request); expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith({ headers: { - ...DEFAULT_HEADERS, - 'x-opaque-id': 'from request', + [headerKey]: 'foo', + 'x-opaque-id': expect.any(String), }, }); }); - it('respect the precedence of config headers over default headers', () => { + it('respect the precedence of request headers over default headers', () => { const headerKey = Object.keys(DEFAULT_HEADERS)[0]; const config = createConfig({ - customHeaders: { - [headerKey]: 'foo', - }, + requestHeadersWhitelist: [headerKey], }); getAuthHeaders.mockReturnValue({}); const clusterClient = new ClusterClient(config, logger, 'custom-type', getAuthHeaders); - const request = httpServerMock.createKibanaRequest(); + const request = httpServerMock.createKibanaRequest({ + headers: { [headerKey]: 'foo' }, + }); clusterClient.asScoped(request); @@ -340,16 +336,18 @@ describe('ClusterClient', () => { }); }); - it('respect the precedence of request headers over default headers', () => { - const headerKey = Object.keys(DEFAULT_HEADERS)[0]; + it('respect the precedence of x-opaque-id header over config headers', () => { const config = createConfig({ - requestHeadersWhitelist: [headerKey], + customHeaders: { + 'x-opaque-id': 'from config', + }, }); getAuthHeaders.mockReturnValue({}); const clusterClient = new ClusterClient(config, logger, 'custom-type', getAuthHeaders); const request = httpServerMock.createKibanaRequest({ - headers: { [headerKey]: 'foo' }, + headers: { foo: 'request' }, + kibanaRequestState: { requestId: 'from request', requestUuid: 'ignore-this-id' }, }); clusterClient.asScoped(request); @@ -357,8 +355,8 @@ describe('ClusterClient', () => { expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith({ headers: { - [headerKey]: 'foo', - 'x-opaque-id': expect.any(String), + ...DEFAULT_HEADERS, + 'x-opaque-id': 'from request', }, }); }); From 79b7a9901c1303be15c75ca312cb8093c87dc1f3 Mon Sep 17 00:00:00 2001 From: restrry Date: Tue, 6 Jul 2021 23:10:02 +0300 Subject: [PATCH 34/36] add header size limitation to the server side as well --- .../execution_context_container.ts | 4 +++- .../execution_context_container.test.ts | 23 +++++++++++++++++++ .../execution_context_container.ts | 17 +++++++++++++- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/core/public/execution_context/execution_context_container.ts b/src/core/public/execution_context/execution_context_container.ts index 489a5de30a33bb..9c8e3e269ec883 100644 --- a/src/core/public/execution_context/execution_context_container.ts +++ b/src/core/public/execution_context/execution_context_container.ts @@ -18,7 +18,9 @@ export const BAGGAGE_MAX_PER_NAME_VALUE_PAIRS = 4096; // a single character can use up to 4 bytes const MAX_BAGGAGE_LENGTH = BAGGAGE_MAX_PER_NAME_VALUE_PAIRS / 4; -// the trimmed value in the server logs is better than nothing. +// Limits the header value to max allowed "baggage" header property name-value pair +// It will help us switch to the "baggage" header when it becomes the standard. +// The trimmed value in the logs is better than nothing. function enforceMaxLength(header: string): string { return header.slice(0, MAX_BAGGAGE_LENGTH); } diff --git a/src/core/server/execution_context/execution_context_container.test.ts b/src/core/server/execution_context/execution_context_container.test.ts index cc3a8916474669..46a688c8abdf0a 100644 --- a/src/core/server/execution_context/execution_context_container.test.ts +++ b/src/core/server/execution_context/execution_context_container.test.ts @@ -10,6 +10,7 @@ import { ExecutionContextContainer, getParentContextFrom, BAGGAGE_HEADER, + BAGGAGE_MAX_PER_NAME_VALUE_PAIRS, } from './execution_context_container'; describe('KibanaExecutionContext', () => { @@ -35,6 +36,28 @@ describe('KibanaExecutionContext', () => { const value = new ExecutionContextContainer(context).toString(); expect(value).toMatchInlineSnapshot(`"1234-5678"`); }); + + it('trims a string representation of provided execution context if it is bigger max allowed size', () => { + expect( + new Blob([ + new ExecutionContextContainer({ + requestId: '1234-5678'.repeat(1000), + }).toString(), + ]).size + ).toBeLessThanOrEqual(BAGGAGE_MAX_PER_NAME_VALUE_PAIRS); + + expect( + new Blob([ + new ExecutionContextContainer({ + type: 'test-type'.repeat(1000), + name: 'test-name', + id: '42'.repeat(1000), + description: 'test-descripton', + requestId: '1234-5678', + }).toString(), + ]).size + ).toBeLessThanOrEqual(BAGGAGE_MAX_PER_NAME_VALUE_PAIRS); + }); }); describe('toJSON', () => { diff --git a/src/core/server/execution_context/execution_context_container.ts b/src/core/server/execution_context/execution_context_container.ts index c2c27b2f8c0d5d..71bf4bb96e1b0f 100644 --- a/src/core/server/execution_context/execution_context_container.ts +++ b/src/core/server/execution_context/execution_context_container.ts @@ -28,6 +28,20 @@ function parseHeader(header?: string): KibanaExecutionContext | undefined { } } +// Maximum number of bytes per a single name-value pair allowed by w3c spec +// https://w3c.github.io/baggage/ +export const BAGGAGE_MAX_PER_NAME_VALUE_PAIRS = 4096; + +// a single character can use up to 4 bytes +const MAX_BAGGAGE_LENGTH = BAGGAGE_MAX_PER_NAME_VALUE_PAIRS / 4; + +// Limits the header value to max allowed "baggage" header property name-value pair +// It will help us switch to the "baggage" header when it becomes the standard. +// The trimmed value in the logs is better than nothing. +function enforceMaxLength(header: string): string { + return header.slice(0, MAX_BAGGAGE_LENGTH); +} + /** * @public */ @@ -44,7 +58,8 @@ export class ExecutionContextContainer implements IExecutionContextContainer { toString(): string { const ctx = this.#context; const contextStringified = ctx.type && ctx.id ? `kibana:${ctx.type}:${ctx.id}` : ''; - return contextStringified ? `${ctx.requestId};${contextStringified}` : ctx.requestId; + const result = contextStringified ? `${ctx.requestId};${contextStringified}` : ctx.requestId; + return enforceMaxLength(result); } toJSON(): Readonly { return this.#context; From a368085e0eee687db93e6c69d9ce4f0a7888c949 Mon Sep 17 00:00:00 2001 From: restrry Date: Wed, 7 Jul 2021 15:30:37 +0300 Subject: [PATCH 35/36] fix integration tests --- .../integration_tests/tracing.test.ts | 102 +++++++++--------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/src/core/server/execution_context/integration_tests/tracing.test.ts b/src/core/server/execution_context/integration_tests/tracing.test.ts index 36e40fe5ee973c..1b68f206b81f0c 100644 --- a/src/core/server/execution_context/integration_tests/tracing.test.ts +++ b/src/core/server/execution_context/integration_tests/tracing.test.ts @@ -155,69 +155,69 @@ describe('trace', () => { expect(header).toBe('new-opaque-id'); }); - it('passed to Elasticsearch scoped client calls even if ExecutionContext Serivce is disabled', async () => { - const kibana = kbnTestServer.createRootWithCorePlugins({ - execution_context: { enabled: false }, - plugins: { initialize: false }, - server: { - requestId: { - allowFromAnyIp: true, + describe('ExecutionContext Serivce is disabled', () => { + let rootExecutionContextDisabled: ReturnType; + beforeEach(async () => { + rootExecutionContextDisabled = kbnTestServer.createRootWithCorePlugins({ + execution_context: { enabled: false }, + plugins: { initialize: false }, + server: { + requestId: { + allowFromAnyIp: true, + }, }, - }, - }); - const { http } = await kibana.setup(); - const { createRouter } = http; + }); + }, 30000); - const router = createRouter(''); - router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { - const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping(); - return res.ok({ body: headers || {} }); + afterEach(async () => { + await rootExecutionContextDisabled.shutdown(); }); + it('passed to Elasticsearch scoped client calls even if ExecutionContext Serivce is disabled', async () => { + const { http } = await rootExecutionContextDisabled.setup(); + const { createRouter } = http; + + const router = createRouter(''); + router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { + const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping(); + return res.ok({ body: headers || {} }); + }); - await kibana.start(); + await rootExecutionContextDisabled.start(); - const myOpaqueId = 'my-opaque-id'; - const response = await kbnTestServer.request - .get(kibana, '/execution-context') - .set('x-opaque-id', myOpaqueId) - .expect(200); - - const header = response.body['x-opaque-id']; - expect(header).toBe(myOpaqueId); - }); + const myOpaqueId = 'my-opaque-id'; + const response = await kbnTestServer.request + .get(rootExecutionContextDisabled, '/execution-context') + .set('x-opaque-id', myOpaqueId) + .expect(200); - it('does not pass context if ExecutionContext Serivce is disabled', async () => { - const kibana = kbnTestServer.createRootWithCorePlugins({ - execution_context: { enabled: false }, - plugins: { initialize: false }, - server: { - requestId: { - allowFromAnyIp: true, - }, - }, + const header = response.body['x-opaque-id']; + expect(header).toBe(myOpaqueId); }); - const { http, executionContext } = await kibana.setup(); - const { createRouter } = http; - const router = createRouter(''); - router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { - executionContext.set(parentContext); - const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping(); - return res.ok({ - body: { context: executionContext.get()?.toJSON(), header: headers?.['x-opaque-id'] }, + it('does not pass context if ExecutionContext Serivce is disabled', async () => { + const { http, executionContext } = await rootExecutionContextDisabled.setup(); + const { createRouter } = http; + + const router = createRouter(''); + router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { + executionContext.set(parentContext); + const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping(); + return res.ok({ + body: { context: executionContext.get()?.toJSON(), header: headers?.['x-opaque-id'] }, + }); }); - }); - await kibana.start(); + await rootExecutionContextDisabled.start(); - const myOpaqueId = 'my-opaque-id'; - const response = await kbnTestServer.request - .get(kibana, '/execution-context') - .set('x-opaque-id', myOpaqueId) - .expect(200); + const myOpaqueId = 'my-opaque-id'; + const response = await kbnTestServer.request + .get(rootExecutionContextDisabled, '/execution-context') + .set('x-opaque-id', myOpaqueId) + .expect(200); - expect(response.body).toEqual({ - header: 'my-opaque-id', + expect(response.body).toEqual({ + header: 'my-opaque-id', + }); }); }); }); From 47ee35092f8a7935eada021fa344dfab61c25b25 Mon Sep 17 00:00:00 2001 From: restrry Date: Wed, 7 Jul 2021 16:35:01 +0300 Subject: [PATCH 36/36] address comments --- .../execution_context/integration_tests/tracing.test.ts | 6 +++--- .../test_suites/core_plugins/execution_context.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/server/execution_context/integration_tests/tracing.test.ts b/src/core/server/execution_context/integration_tests/tracing.test.ts index 1b68f206b81f0c..c9de5fb98eb02b 100644 --- a/src/core/server/execution_context/integration_tests/tracing.test.ts +++ b/src/core/server/execution_context/integration_tests/tracing.test.ts @@ -155,7 +155,7 @@ describe('trace', () => { expect(header).toBe('new-opaque-id'); }); - describe('ExecutionContext Serivce is disabled', () => { + describe('ExecutionContext Service is disabled', () => { let rootExecutionContextDisabled: ReturnType; beforeEach(async () => { rootExecutionContextDisabled = kbnTestServer.createRootWithCorePlugins({ @@ -172,7 +172,7 @@ describe('trace', () => { afterEach(async () => { await rootExecutionContextDisabled.shutdown(); }); - it('passed to Elasticsearch scoped client calls even if ExecutionContext Serivce is disabled', async () => { + it('passed to Elasticsearch scoped client calls even if ExecutionContext Service is disabled', async () => { const { http } = await rootExecutionContextDisabled.setup(); const { createRouter } = http; @@ -194,7 +194,7 @@ describe('trace', () => { expect(header).toBe(myOpaqueId); }); - it('does not pass context if ExecutionContext Serivce is disabled', async () => { + it('does not pass context if ExecutionContext Service is disabled', async () => { const { http, executionContext } = await rootExecutionContextDisabled.setup(); const { createRouter } = http; diff --git a/test/plugin_functional/test_suites/core_plugins/execution_context.ts b/test/plugin_functional/test_suites/core_plugins/execution_context.ts index 51312872678f20..21bcddd32bc191 100644 --- a/test/plugin_functional/test_suites/core_plugins/execution_context.ts +++ b/test/plugin_functional/test_suites/core_plugins/execution_context.ts @@ -20,7 +20,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide await PageObjects.common.navigateToApp('home'); }); - it('manually created context', async () => { + it('passes plugin-specific execution context to Elasticsearch server', async () => { expect( await browser.execute(async () => { const coreStart = window._coreProvider.start.core;