[ML] Transforms: Adding execution context to ES requests. (#153649)

Part of https://github.com/elastic/kibana/issues/147378

- Similar to #148746, adds execution context to transform API endpoints.
- Moves `createExecutionContext` to package `@kbn/ml-route-utils`.
This commit is contained in:
Walter Rafelsberger 2023-03-31 17:19:18 +02:00 committed by GitHub
parent 3930f7aa99
commit 33599ad414
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 194 additions and 57 deletions

1
.github/CODEOWNERS vendored
View file

@ -459,6 +459,7 @@ x-pack/packages/ml/local_storage @elastic/ml-ui
x-pack/packages/ml/nested_property @elastic/ml-ui
x-pack/plugins/ml @elastic/ml-ui
x-pack/packages/ml/query_utils @elastic/ml-ui
x-pack/packages/ml/route_utils @elastic/ml-ui
x-pack/packages/ml/string_hash @elastic/ml-ui
x-pack/packages/ml/url_state @elastic/ml-ui
packages/kbn-monaco @elastic/appex-sharedux

View file

@ -474,6 +474,7 @@
"@kbn/ml-nested-property": "link:x-pack/packages/ml/nested_property",
"@kbn/ml-plugin": "link:x-pack/plugins/ml",
"@kbn/ml-query-utils": "link:x-pack/packages/ml/query_utils",
"@kbn/ml-route-utils": "link:x-pack/packages/ml/route_utils",
"@kbn/ml-string-hash": "link:x-pack/packages/ml/string_hash",
"@kbn/ml-url-state": "link:x-pack/packages/ml/url_state",
"@kbn/monaco": "link:packages/kbn-monaco",

View file

@ -912,6 +912,8 @@
"@kbn/ml-plugin/*": ["x-pack/plugins/ml/*"],
"@kbn/ml-query-utils": ["x-pack/packages/ml/query_utils"],
"@kbn/ml-query-utils/*": ["x-pack/packages/ml/query_utils/*"],
"@kbn/ml-route-utils": ["x-pack/packages/ml/route_utils"],
"@kbn/ml-route-utils/*": ["x-pack/packages/ml/route_utils/*"],
"@kbn/ml-string-hash": ["x-pack/packages/ml/string_hash"],
"@kbn/ml-string-hash/*": ["x-pack/packages/ml/string_hash/*"],
"@kbn/ml-url-state": ["x-pack/packages/ml/url_state"],

View file

@ -0,0 +1,3 @@
# @kbn/ml-route-utils
Route utils maintained by the ML UI team.

View file

@ -0,0 +1,8 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export { createExecutionContext } from './src/create_execution_context';

View file

@ -0,0 +1,12 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../../..',
roots: ['<rootDir>/x-pack/packages/ml/route_utils'],
};

View file

@ -0,0 +1,5 @@
{
"type": "shared-common",
"id": "@kbn/ml-route-utils",
"owner": "@elastic/ml-ui"
}

View file

@ -0,0 +1,6 @@
{
"name": "@kbn/ml-route-utils",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0"
}

View file

@ -0,0 +1,36 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { CoreStart } from '@kbn/core/server';
import { createExecutionContext } from './create_execution_context';
const coreStartMock = {
executionContext: {
getAsLabels: () => ({ page: 'the-page' }),
},
} as unknown as CoreStart;
describe('createExecutionContext', () => {
it('returns an execution context based on execution context labels', () => {
expect(createExecutionContext(coreStartMock, 'the-name')).toEqual({
type: 'application',
name: 'the-name',
id: 'the-page',
page: 'the-page',
});
});
it('returns an execution context based on a supplied id', () => {
expect(createExecutionContext(coreStartMock, 'the-name', 'the-id')).toEqual({
type: 'application',
name: 'the-name',
id: 'the-id',
page: 'the-page',
});
});
});

View file

@ -0,0 +1,34 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { CoreStart } from '@kbn/core/server';
/**
* Creates an execution context to be passed on as part of ES queries.
* This allows you to identify the source triggering a request when debugging slow logs.
*
* @param coreStart Kibana CoreStart
* @param name Context name, usually the plugin id
* @param id Optional context id, can be used to override the default usage of page as id
* @param type Optional context type, defaults to `application`.
* @returns
*/
export function createExecutionContext(
coreStart: CoreStart,
name: string,
id?: string,
type = 'application'
) {
const labels = coreStart.executionContext.getAsLabels();
const page = labels.page as string;
return {
type,
name,
id: id ?? page,
page,
};
}

View file

@ -0,0 +1,21 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": [
"jest",
"node",
"react"
]
},
"include": [
"**/*.ts",
"**/*.tsx",
],
"exclude": [
"target/**/*"
],
"kbn_references": [
"@kbn/core",
]
}

View file

@ -13,14 +13,14 @@ import type {
RequestHandler,
SavedObjectsClientContract,
CoreSetup,
CoreStart,
} from '@kbn/core/server';
import type { SpacesPluginSetup } from '@kbn/spaces-plugin/server';
import type { SecurityPluginSetup } from '@kbn/security-plugin/server';
import type { AlertingApiRequestHandlerContext } from '@kbn/alerting-plugin/server';
import type { PluginStart as DataViewsPluginStart } from '@kbn/data-views-plugin/server';
import type { DataViewsService } from '@kbn/data-views-plugin/common';
import { createExecutionContext } from '@kbn/ml-route-utils';
import { PLUGIN_ID } from '../../common/constants/app';
import { mlSavedObjectServiceFactory, MLSavedObjectService } from '../saved_objects';
import type { MlLicense } from '../../common/license';
@ -117,7 +117,7 @@ export class RouteGuard {
);
const [coreStart] = await this._getStartServices();
const executionContext = await createExecutionContext(coreStart, request.route.path);
const executionContext = createExecutionContext(coreStart, PLUGIN_ID, request.route.path);
return await coreStart.executionContext.withContext(executionContext, () =>
handler({
@ -138,14 +138,3 @@ export class RouteGuard {
};
}
}
async function createExecutionContext(coreStart: CoreStart, id?: string) {
const labels = coreStart.executionContext.getAsLabels();
const page = labels.page as string;
return {
type: 'application',
name: PLUGIN_ID,
id: id ?? page,
page,
};
}

View file

@ -86,5 +86,6 @@
"@kbn/saved-objects-finder-plugin",
"@kbn/monaco",
"@kbn/repo-info",
"@kbn/ml-route-utils",
],
}

View file

@ -11,7 +11,7 @@ import { CoreSetup, CoreStart, Plugin, Logger, PluginInitializerContext } from '
import { LicenseType } from '@kbn/licensing-plugin/common/types';
import { PluginSetupDependencies, PluginStartDependencies } from './types';
import { ApiRoutes } from './routes';
import { registerRoutes } from './routes';
import { License } from './services';
import { registerTransformHealthRuleType } from './lib/alerting';
@ -27,38 +27,18 @@ const PLUGIN = {
};
export class TransformServerPlugin implements Plugin<{}, void, any, any> {
private readonly apiRoutes: ApiRoutes;
private readonly license: License;
private readonly logger: Logger;
private fieldFormatsStart: PluginStartDependencies['fieldFormats'] | null = null;
constructor(initContext: PluginInitializerContext) {
this.logger = initContext.logger.get();
this.apiRoutes = new ApiRoutes();
this.license = new License();
}
setup(
{ http, getStartServices, elasticsearch }: CoreSetup<PluginStartDependencies>,
{ licensing, features, alerting }: PluginSetupDependencies
): {} {
const router = http.createRouter();
this.license.setup(
{
pluginId: PLUGIN.id,
minimumLicenseType: PLUGIN.minimumLicenseType,
defaultErrorMessage: i18n.translate('xpack.transform.licenseCheckErrorMessage', {
defaultMessage: 'License check failed',
}),
},
{
licensing,
logger: this.logger,
}
);
features.registerElasticsearchFeature({
id: PLUGIN.id,
management: {
@ -73,10 +53,24 @@ export class TransformServerPlugin implements Plugin<{}, void, any, any> {
],
});
this.apiRoutes.setup({
router,
license: this.license,
getStartServices,
getStartServices().then(([coreStart, { dataViews }]) => {
const license = new License({
pluginId: PLUGIN.id,
minimumLicenseType: PLUGIN.minimumLicenseType,
defaultErrorMessage: i18n.translate('xpack.transform.licenseCheckErrorMessage', {
defaultMessage: 'License check failed',
}),
licensing,
logger: this.logger,
coreStart,
});
registerRoutes({
router: http.createRouter(),
license,
dataViews,
coreStart,
});
});
if (alerting) {

View file

@ -79,7 +79,7 @@ enum TRANSFORM_ACTIONS {
}
export function registerTransformsRoutes(routeDependencies: RouteDependencies) {
const { router, license, getStartServices } = routeDependencies;
const { router, license, coreStart, dataViews } = routeDependencies;
/**
* @apiGroup Transforms
*
@ -314,7 +314,7 @@ export function registerTransformsRoutes(routeDependencies: RouteDependencies) {
license.guardApiRoute<undefined, undefined, DeleteTransformsRequestSchema>(
async (ctx, req, res) => {
try {
const [{ savedObjects, elasticsearch }, { dataViews }] = await getStartServices();
const { savedObjects, elasticsearch } = coreStart;
const savedObjectsClient = savedObjects.getScopedClient(req);
const esClient = elasticsearch.client.asScoped(req).asCurrentUser;

View file

@ -15,10 +15,8 @@ import { API_BASE_PATH } from '../../common/constants';
export const addBasePath = (uri: string): string => `${API_BASE_PATH}${uri}`;
export class ApiRoutes {
setup(dependencies: RouteDependencies) {
registerFieldHistogramsRoutes(dependencies);
registerPrivilegesRoute(dependencies);
registerTransformsRoutes(dependencies);
}
export function registerRoutes(dependencies: RouteDependencies) {
registerFieldHistogramsRoutes(dependencies);
registerPrivilegesRoute(dependencies);
registerTransformsRoutes(dependencies);
}

View file

@ -7,6 +7,7 @@
import { Logger } from '@kbn/core/server';
import {
CoreStart,
IKibanaResponse,
KibanaRequest,
KibanaResponseFactory,
@ -16,6 +17,9 @@ import {
import { LicensingPluginSetup, LicenseType } from '@kbn/licensing-plugin/server';
import type { AlertingApiRequestHandlerContext } from '@kbn/alerting-plugin/server';
import { createExecutionContext } from '@kbn/ml-route-utils';
import { PLUGIN } from '../../common/constants';
export interface LicenseStatus {
isValid: boolean;
@ -27,6 +31,9 @@ interface SetupSettings {
pluginId: string;
minimumLicenseType: LicenseType;
defaultErrorMessage: string;
licensing: LicensingPluginSetup;
logger: Logger;
coreStart: CoreStart;
}
type TransformRequestHandlerContext = CustomRequestHandlerContext<{
@ -34,16 +41,22 @@ type TransformRequestHandlerContext = CustomRequestHandlerContext<{
}>;
export class License {
private coreStart: CoreStart;
private licenseStatus: LicenseStatus = {
isValid: false,
isSecurityEnabled: false,
message: 'Invalid License',
};
setup(
{ pluginId, minimumLicenseType, defaultErrorMessage }: SetupSettings,
{ licensing, logger }: { licensing: LicensingPluginSetup; logger: Logger }
) {
constructor({
pluginId,
minimumLicenseType,
defaultErrorMessage,
licensing,
logger,
coreStart,
}: SetupSettings) {
this.coreStart = coreStart;
licensing.license$.subscribe((license) => {
const { state, message } = license.check(pluginId, minimumLicenseType);
const hasRequiredLicense = state === 'valid';
@ -74,12 +87,17 @@ export class License {
) {
const license = this;
return function licenseCheck(
return async function licenseCheck(
ctx: TransformRequestHandlerContext,
request: KibanaRequest<Params, Query, Body>,
response: KibanaResponseFactory
): IKibanaResponse<any> | Promise<IKibanaResponse<any>> {
): Promise<IKibanaResponse<any>> {
const licenseStatus = license.getStatus();
const executionContext = createExecutionContext(
license.coreStart,
PLUGIN.ID,
request.route.path
);
if (!licenseStatus.isValid) {
return response.customError({
@ -90,7 +108,9 @@ export class License {
});
}
return handler(ctx, request, response);
return await license.coreStart.executionContext.withContext(executionContext, () =>
handler(ctx, request, response)
);
};
}

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { IRouter, CoreSetup } from '@kbn/core/server';
import { IRouter, CoreStart } from '@kbn/core/server';
import { PluginStart as DataViewsServerPluginStart } from '@kbn/data-views-plugin/server';
import { LicensingPluginSetup } from '@kbn/licensing-plugin/server';
import { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-plugin/server';
@ -28,5 +28,6 @@ export interface PluginStartDependencies {
export interface RouteDependencies {
router: IRouter;
license: License;
getStartServices: CoreSetup<PluginStartDependencies>['getStartServices'];
coreStart: CoreStart;
dataViews: DataViewsServerPluginStart;
}

View file

@ -55,7 +55,8 @@
"@kbn/unified-field-list-plugin",
"@kbn/shared-ux-router",
"@kbn/saved-objects-management-plugin",
"@kbn/saved-objects-finder-plugin"
"@kbn/saved-objects-finder-plugin",
"@kbn/ml-route-utils"
],
"exclude": [
"target/**/*",

View file

@ -4561,6 +4561,10 @@
version "0.0.0"
uid ""
"@kbn/ml-route-utils@link:x-pack/packages/ml/route_utils":
version "0.0.0"
uid ""
"@kbn/ml-string-hash@link:x-pack/packages/ml/string_hash":
version "0.0.0"
uid ""