From 16242ca5d1da17b0cb408b951aebae95a67d5ca9 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 20 Nov 2024 16:36:33 +0100 Subject: [PATCH] [Synthetics] SLO Availability sync delay field to use timestamp instead of event.ingested !! (#199308) ## Summary Fixes https://github.com/elastic/kibana/issues/196548 SLO Availability sync delay field to use `@timestamp` instead of `event.ingested` !! ### Testing - Make sure Synthetics availability SLOs works as expected in serverless and stateful - Make sure when SLO is updated, it continues to work --- .../slo/server/plugin.ts | 1 + .../slo/server/routes/register_routes.ts | 20 +++++++++++-- .../slo/server/routes/slo/route.ts | 29 ++++++++++++++----- .../slo/server/routes/types.ts | 15 ++++++++-- .../synthetics_availability.test.ts | 24 +++++++-------- .../synthetics_availability.ts | 7 +++-- .../transform_generator.ts | 3 +- .../server/services/transform_manager.test.ts | 24 ++++++++++----- .../slo/server/services/transform_manager.ts | 13 +++++++-- 9 files changed, 97 insertions(+), 39 deletions(-) diff --git a/x-pack/plugins/observability_solution/slo/server/plugin.ts b/x-pack/plugins/observability_solution/slo/server/plugin.ts index d7d002d26aa03..7699cbe5f1404 100644 --- a/x-pack/plugins/observability_solution/slo/server/plugin.ts +++ b/x-pack/plugins/observability_solution/slo/server/plugin.ts @@ -141,6 +141,7 @@ export class SLOPlugin }, logger: this.logger, repository: getSloServerRouteRepository({ isServerless: this.isServerless }), + isServerless: this.isServerless, }); core diff --git a/x-pack/plugins/observability_solution/slo/server/routes/register_routes.ts b/x-pack/plugins/observability_solution/slo/server/routes/register_routes.ts index fd0b18c210041..6e3c02a5b921b 100644 --- a/x-pack/plugins/observability_solution/slo/server/routes/register_routes.ts +++ b/x-pack/plugins/observability_solution/slo/server/routes/register_routes.ts @@ -7,16 +7,32 @@ import { CoreSetup, Logger } from '@kbn/core/server'; import { ServerRoute, registerRoutes } from '@kbn/server-route-repository'; import { ServerRouteCreateOptions } from '@kbn/server-route-repository-utils'; -import { SLORoutesDependencies } from './types'; +import { SLORequestHandlerContext, SLORoutesDependencies } from './types'; interface RegisterRoutes { core: CoreSetup; repository: Record>; logger: Logger; dependencies: SLORoutesDependencies; + isServerless: boolean; } -export function registerServerRoutes({ repository, core, logger, dependencies }: RegisterRoutes) { +export function registerServerRoutes({ + repository, + core, + logger, + dependencies, + isServerless, +}: RegisterRoutes) { + core.http.registerRouteHandlerContext( + 'slo', + async (_context, _request) => { + return { + isServerless, + }; + } + ); + registerRoutes({ repository, dependencies, diff --git a/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts b/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts index 9e63a4b02fe7b..7f3b395c7adba 100644 --- a/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts +++ b/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts @@ -107,6 +107,7 @@ const createSLORoute = createSloServerRoute({ handler: async ({ context, response, params, logger, request, plugins, corePlugins }) => { await assertPlatinumLicense(plugins); + const sloContext = await context.slo; const dataViews = await plugins.dataViews.start(); const core = await context.core; const scopedClusterClient = core.elasticsearch.client; @@ -124,7 +125,8 @@ const createSLORoute = createSloServerRoute({ scopedClusterClient, logger, spaceId, - dataViewsService + dataViewsService, + sloContext.isServerless ); const summaryTransformManager = new DefaultSummaryTransformManager( new DefaultSummaryTransformGenerator(), @@ -156,6 +158,7 @@ const inspectSLORoute = createSloServerRoute({ handler: async ({ context, params, logger, request, plugins, corePlugins }) => { await assertPlatinumLicense(plugins); + const sloContext = await context.slo; const dataViews = await plugins.dataViews.start(); const spaceId = await getSpaceId(plugins, request); const basePath = corePlugins.http.basePath; @@ -170,7 +173,8 @@ const inspectSLORoute = createSloServerRoute({ scopedClusterClient, logger, spaceId, - dataViewsService + dataViewsService, + sloContext.isServerless ); const summaryTransformManager = new DefaultSummaryTransformManager( new DefaultSummaryTransformGenerator(), @@ -206,6 +210,7 @@ const updateSLORoute = createSloServerRoute({ const spaceId = await getSpaceId(plugins, request); const dataViews = await plugins.dataViews.start(); + const sloContext = await context.slo; const basePath = corePlugins.http.basePath; const core = await context.core; const scopedClusterClient = core.elasticsearch.client; @@ -218,7 +223,8 @@ const updateSLORoute = createSloServerRoute({ scopedClusterClient, logger, spaceId, - dataViewsService + dataViewsService, + sloContext.isServerless ); const summaryTransformManager = new DefaultSummaryTransformManager( new DefaultSummaryTransformGenerator(), @@ -254,6 +260,7 @@ const deleteSLORoute = createSloServerRoute({ const spaceId = await getSpaceId(plugins, request); const dataViews = await plugins.dataViews.start(); + const sloContext = await context.slo; const core = await context.core; const scopedClusterClient = core.elasticsearch.client; const esClient = core.elasticsearch.client.asCurrentUser; @@ -270,7 +277,8 @@ const deleteSLORoute = createSloServerRoute({ scopedClusterClient, logger, spaceId, - dataViewsService + dataViewsService, + sloContext.isServerless ); const summaryTransformManager = new DefaultSummaryTransformManager( @@ -331,7 +339,7 @@ const enableSLORoute = createSloServerRoute({ const spaceId = await getSpaceId(plugins, request); const dataViews = await plugins.dataViews.start(); - + const sloContext = await context.slo; const core = await context.core; const scopedClusterClient = core.elasticsearch.client; const soClient = core.savedObjects.client; @@ -343,7 +351,8 @@ const enableSLORoute = createSloServerRoute({ scopedClusterClient, logger, spaceId, - dataViewsService + dataViewsService, + sloContext.isServerless ); const summaryTransformManager = new DefaultSummaryTransformManager( new DefaultSummaryTransformGenerator(), @@ -372,6 +381,7 @@ const disableSLORoute = createSloServerRoute({ const spaceId = await getSpaceId(plugins, request); const dataViews = await plugins.dataViews.start(); + const sloContext = await context.slo; const core = await context.core; const scopedClusterClient = core.elasticsearch.client; const soClient = core.savedObjects.client; @@ -383,7 +393,8 @@ const disableSLORoute = createSloServerRoute({ scopedClusterClient, logger, spaceId, - dataViewsService + dataViewsService, + sloContext.isServerless ); const summaryTransformManager = new DefaultSummaryTransformManager( new DefaultSummaryTransformGenerator(), @@ -408,6 +419,7 @@ const resetSLORoute = createSloServerRoute({ handler: async ({ context, request, params, logger, plugins, corePlugins }) => { await assertPlatinumLicense(plugins); + const sloContext = await context.slo; const dataViews = await plugins.dataViews.start(); const spaceId = await getSpaceId(plugins, request); const core = await context.core; @@ -423,7 +435,8 @@ const resetSLORoute = createSloServerRoute({ scopedClusterClient, logger, spaceId, - dataViewsService + dataViewsService, + sloContext.isServerless ); const summaryTransformManager = new DefaultSummaryTransformManager( new DefaultSummaryTransformGenerator(), diff --git a/x-pack/plugins/observability_solution/slo/server/routes/types.ts b/x-pack/plugins/observability_solution/slo/server/routes/types.ts index cb5057cee4056..37937cf8c5688 100644 --- a/x-pack/plugins/observability_solution/slo/server/routes/types.ts +++ b/x-pack/plugins/observability_solution/slo/server/routes/types.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { CoreSetup } from '@kbn/core/server'; +import { CoreSetup, CustomRequestHandlerContext } from '@kbn/core/server'; import type { DefaultRouteHandlerResources } from '@kbn/server-route-repository'; import { SLOPluginSetupDependencies, SLOPluginStartDependencies } from '../types'; @@ -21,4 +21,15 @@ export interface SLORoutesDependencies { corePlugins: CoreSetup; } -export type SLORouteHandlerResources = SLORoutesDependencies & DefaultRouteHandlerResources; +export type SLORouteHandlerResources = SLORoutesDependencies & + DefaultRouteHandlerResources & { + context: SLORequestHandlerContext; + }; + +export interface SLORouteContext { + isServerless: boolean; +} + +export type SLORequestHandlerContext = CustomRequestHandlerContext<{ + slo: Promise; +}>; diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/synthetics_availability.test.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/synthetics_availability.test.ts index 565a0d56d1ff4..fa40ab9cc1e8d 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/synthetics_availability.test.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/synthetics_availability.test.ts @@ -19,7 +19,7 @@ describe('Synthetics Availability Transform Generator', () => { it('returns the expected transform params', async () => { const slo = createSLO({ id: 'irrelevant', indicator: createSyntheticsAvailabilityIndicator() }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo, spaceId, dataViewsService, false); expect(transform).toMatchSnapshot(); expect(transform.source.query?.bool?.filter).toContainEqual({ @@ -34,7 +34,7 @@ describe('Synthetics Availability Transform Generator', () => { id: 'irrelevant', indicator: createSyntheticsAvailabilityIndicator(), }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo, spaceId, dataViewsService, false); expect(transform.pivot?.group_by).toEqual( expect.objectContaining({ @@ -58,7 +58,7 @@ describe('Synthetics Availability Transform Generator', () => { indicator: createSyntheticsAvailabilityIndicator(), groupBy: ['host.name'], }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo, spaceId, dataViewsService, false); expect(transform.pivot?.group_by).not.toEqual( expect.objectContaining({ @@ -94,7 +94,7 @@ describe('Synthetics Availability Transform Generator', () => { indicator: createSyntheticsAvailabilityIndicator(), groupBy, }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo, spaceId, dataViewsService, false); expect(transform.pivot?.group_by).toEqual( expect.objectContaining({ @@ -121,7 +121,7 @@ describe('Synthetics Availability Transform Generator', () => { indicator: createSyntheticsAvailabilityIndicator(), groupBy, }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo, spaceId, dataViewsService, false); expect(transform.pivot?.group_by).toEqual( expect.objectContaining({ @@ -146,7 +146,7 @@ describe('Synthetics Availability Transform Generator', () => { indicator: createSyntheticsAvailabilityIndicator(), groupBy, }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo, spaceId, dataViewsService, false); expect(transform.pivot?.group_by).toEqual( expect.objectContaining({ @@ -161,7 +161,7 @@ describe('Synthetics Availability Transform Generator', () => { it('filters by summary.final_attempt', async () => { const slo = createSLO({ id: 'irrelevant', indicator: createSyntheticsAvailabilityIndicator() }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo, spaceId, dataViewsService, false); expect(transform.source.query?.bool?.filter).toContainEqual({ term: { @@ -186,7 +186,7 @@ describe('Synthetics Availability Transform Generator', () => { }, } as SLODefinition['indicator'], }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo, spaceId, dataViewsService, false); expect(transform.source.query?.bool?.filter).toContainEqual({ terms: { @@ -216,7 +216,7 @@ describe('Synthetics Availability Transform Generator', () => { }, } as SLODefinition['indicator'], }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo, spaceId, dataViewsService, false); expect(transform.source.query?.bool?.filter).toContainEqual({ terms: { @@ -246,7 +246,7 @@ describe('Synthetics Availability Transform Generator', () => { }, } as SLODefinition['indicator'], }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo, spaceId, dataViewsService, false); expect(transform.source.query?.bool?.filter).toContainEqual({ terms: { @@ -262,7 +262,7 @@ describe('Synthetics Availability Transform Generator', () => { it('filters by space', async () => { const slo = createSLO({ id: 'irrelevant', indicator: createSyntheticsAvailabilityIndicator() }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo, spaceId, dataViewsService, false); expect(transform.source.query?.bool?.filter).toContainEqual({ term: { @@ -281,7 +281,7 @@ describe('Synthetics Availability Transform Generator', () => { }, }); - const transform = await generator.getTransformParams(slo, 'default', dataViewsService); + const transform = await generator.getTransformParams(slo, 'default', dataViewsService, false); // @ts-ignore const rangeFilter = transform.source.query.bool.filter.find((f) => 'range' in f); diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/synthetics_availability.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/synthetics_availability.ts index e15c1d09a2044..285820f908182 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/synthetics_availability.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/synthetics_availability.ts @@ -31,7 +31,8 @@ export class SyntheticsAvailabilityTransformGenerator extends TransformGenerator public async getTransformParams( slo: SLODefinition, spaceId: string, - dataViewService: DataViewsService + dataViewService: DataViewsService, + isServerless: boolean ): Promise { if (!syntheticsAvailabilityIndicatorSchema.is(slo.indicator)) { throw new InvalidTransformError(`Cannot handle SLO of indicator type: ${slo.indicator.type}`); @@ -44,7 +45,7 @@ export class SyntheticsAvailabilityTransformGenerator extends TransformGenerator this.buildDestination(slo), this.buildGroupBy(slo, slo.indicator), this.buildAggregations(slo), - this.buildSettings(slo, 'event.ingested'), + this.buildSettings(slo, isServerless ? '@timestamp' : 'event.ingested'), slo ); } @@ -56,7 +57,7 @@ export class SyntheticsAvailabilityTransformGenerator extends TransformGenerator private buildGroupBy(slo: SLODefinition, indicator: SyntheticsAvailabilityIndicator) { // These are the group by fields that will be used in `groupings` key // in the summary and rollup documents. For Synthetics, we want to use the - // user-readible `monitor.name` and `observer.geo.name` fields by default, + // user-readable `monitor.name` and `observer.geo.name` fields by default, // unless otherwise specified by the user. const flattenedGroupBy = [slo.groupBy].flat().filter((value) => !!value); const groupings = diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/transform_generator.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/transform_generator.ts index 8ae6eeb52c9be..25b0dc161661c 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/transform_generator.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/transform_generator.ts @@ -18,7 +18,8 @@ export abstract class TransformGenerator { public abstract getTransformParams( slo: SLODefinition, spaceId: string, - dataViewService: DataViewsService + dataViewService: DataViewsService, + isServerless: boolean ): Promise; public buildCommonRuntimeMappings(slo: SLODefinition, dataView?: DataView): MappingRuntimeFields { diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_manager.test.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_manager.test.ts index b7b5d7ba4fcd9..e837db4e88dc2 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_manager.test.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_manager.test.ts @@ -51,7 +51,8 @@ describe('TransformManager', () => { scopedClusterClientMock, loggerMock, spaceId, - dataViewsService + dataViewsService, + false ); await expect( @@ -69,7 +70,8 @@ describe('TransformManager', () => { scopedClusterClientMock, loggerMock, spaceId, - dataViewsService + dataViewsService, + false ); await expect( @@ -90,7 +92,8 @@ describe('TransformManager', () => { scopedClusterClientMock, loggerMock, spaceId, - dataViewsService + dataViewsService, + false ); const slo = createSLO({ indicator: createAPMTransactionErrorRateIndicator() }); @@ -114,7 +117,8 @@ describe('TransformManager', () => { scopedClusterClientMock, loggerMock, spaceId, - dataViewsService + dataViewsService, + false ); await transformManager.preview('slo-transform-id'); @@ -136,7 +140,8 @@ describe('TransformManager', () => { scopedClusterClientMock, loggerMock, spaceId, - dataViewsService + dataViewsService, + false ); await transformManager.start('slo-transform-id'); @@ -158,7 +163,8 @@ describe('TransformManager', () => { scopedClusterClientMock, loggerMock, spaceId, - dataViewsService + dataViewsService, + false ); await transformManager.stop('slo-transform-id'); @@ -180,7 +186,8 @@ describe('TransformManager', () => { scopedClusterClientMock, loggerMock, spaceId, - dataViewsService + dataViewsService, + false ); await transformManager.uninstall('slo-transform-id'); @@ -203,7 +210,8 @@ describe('TransformManager', () => { scopedClusterClientMock, loggerMock, spaceId, - dataViewsService + dataViewsService, + false ); await transformManager.uninstall('slo-transform-id'); diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_manager.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_manager.ts index 7e5ddce8bcad6..aed9931822bdc 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_manager.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_manager.ts @@ -31,7 +31,8 @@ export class DefaultTransformManager implements TransformManager { private scopedClusterClient: IScopedClusterClient, private logger: Logger, private spaceId: string, - private dataViewService: DataViewsService + private dataViewService: DataViewsService, + private isServerless: boolean ) {} async install(slo: SLODefinition): Promise { @@ -44,7 +45,8 @@ export class DefaultTransformManager implements TransformManager { const transformParams = await generator.getTransformParams( slo, this.spaceId, - this.dataViewService + this.dataViewService, + this.isServerless ); try { await retryTransientEsErrors( @@ -72,7 +74,12 @@ export class DefaultTransformManager implements TransformManager { throw new Error(`Unsupported indicator type [${slo.indicator.type}]`); } - return await generator.getTransformParams(slo, this.spaceId, this.dataViewService); + return await generator.getTransformParams( + slo, + this.spaceId, + this.dataViewService, + this.isServerless + ); } async preview(transformId: string): Promise {