From 98db4d6f75f05eba1682e0e903bf2d9c909b37f2 Mon Sep 17 00:00:00 2001 From: Kevin Delemme Date: Fri, 1 Nov 2024 11:36:41 -0400 Subject: [PATCH] fix(slo): Override transform _id to ensure uniqness (#198610) --- .../packages/kbn-slo-schema/src/schema/slo.ts | 31 ++++- x-pack/packages/kbn-slo-schema/tsconfig.json | 2 +- .../ingest_templates/slo_pipeline_template.ts | 6 + .../__snapshots__/create_slo.test.ts.snap | 6 + .../__snapshots__/reset_slo.test.ts.snap | 6 + .../synthetics_availability.test.ts.snap | 117 +++++++++++++++++ .../transform_generator.test.ts.snap | 73 +++++++++++ .../synthetics_availability.test.ts | 119 +----------------- .../transform_generator.test.ts | 54 +------- 9 files changed, 248 insertions(+), 166 deletions(-) create mode 100644 x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/synthetics_availability.test.ts.snap create mode 100644 x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/transform_generator.test.ts.snap diff --git a/x-pack/packages/kbn-slo-schema/src/schema/slo.ts b/x-pack/packages/kbn-slo-schema/src/schema/slo.ts index dcf18d0e3a82e..0576f1cf328eb 100644 --- a/x-pack/packages/kbn-slo-schema/src/schema/slo.ts +++ b/x-pack/packages/kbn-slo-schema/src/schema/slo.ts @@ -6,6 +6,7 @@ */ import * as t from 'io-ts'; +import { Either } from 'fp-ts/Either'; import { allOrAnyStringOrArray, dateType } from './common'; import { durationType } from './duration'; import { indicatorSchema } from './indicators'; @@ -36,7 +37,35 @@ const groupBySchema = allOrAnyStringOrArray; const optionalSettingsSchema = t.partial({ ...settingsSchema.props }); const tagsSchema = t.array(t.string); -const sloIdSchema = t.string; +// id cannot contain special characters and spaces +const sloIdSchema = new t.Type( + 'sloIdSchema', + t.string.is, + (input, context): Either => { + if (typeof input === 'string') { + const valid = isValidId(input); + if (!valid) { + return t.failure( + input, + context, + 'Invalid slo id, must be between 8 and 48 characters and contain only letters, numbers, hyphens, and underscores' + ); + } + + return t.success(input); + } else { + return t.failure(input, context); + } + }, + t.identity +); + +function isValidId(id: string): boolean { + const MIN_ID_LENGTH = 8; + const MAX_ID_LENGTH = 48; + const validLength = MIN_ID_LENGTH <= id.length && id.length <= MAX_ID_LENGTH; + return validLength && /^[a-z0-9-_]+$/.test(id); +} const sloDefinitionSchema = t.type({ id: sloIdSchema, diff --git a/x-pack/packages/kbn-slo-schema/tsconfig.json b/x-pack/packages/kbn-slo-schema/tsconfig.json index bc9fd2fdede8a..cd411fff0db4a 100644 --- a/x-pack/packages/kbn-slo-schema/tsconfig.json +++ b/x-pack/packages/kbn-slo-schema/tsconfig.json @@ -12,7 +12,7 @@ ], "kbn_references": [ "@kbn/std", - "@kbn/io-ts-utils" + "@kbn/io-ts-utils", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/observability_solution/slo/server/assets/ingest_templates/slo_pipeline_template.ts b/x-pack/plugins/observability_solution/slo/server/assets/ingest_templates/slo_pipeline_template.ts index c378cd745397c..dc078f351eb3e 100644 --- a/x-pack/plugins/observability_solution/slo/server/assets/ingest_templates/slo_pipeline_template.ts +++ b/x-pack/plugins/observability_solution/slo/server/assets/ingest_templates/slo_pipeline_template.ts @@ -17,6 +17,12 @@ export const getSLOPipelineTemplate = (slo: SLODefinition) => ({ id: getSLOPipelineId(slo.id, slo.revision), description: `Ingest pipeline for SLO rollup data [id: ${slo.id}, revision: ${slo.revision}]`, processors: [ + { + set: { + field: '_id', + value: `{{{_id}}}-${slo.id}-${slo.revision}`, + }, + }, { set: { field: 'event.ingested', diff --git a/x-pack/plugins/observability_solution/slo/server/services/__snapshots__/create_slo.test.ts.snap b/x-pack/plugins/observability_solution/slo/server/services/__snapshots__/create_slo.test.ts.snap index d747d5083cd28..46acf96dde5c3 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/__snapshots__/create_slo.test.ts.snap +++ b/x-pack/plugins/observability_solution/slo/server/services/__snapshots__/create_slo.test.ts.snap @@ -12,6 +12,12 @@ Array [ "description": "Ingest pipeline for SLO rollup data [id: unique-id, revision: 1]", "id": ".slo-observability.sli.pipeline-unique-id-1", "processors": Array [ + Object { + "set": Object { + "field": "_id", + "value": "{{{_id}}}-unique-id-1", + }, + }, Object { "set": Object { "field": "event.ingested", diff --git a/x-pack/plugins/observability_solution/slo/server/services/__snapshots__/reset_slo.test.ts.snap b/x-pack/plugins/observability_solution/slo/server/services/__snapshots__/reset_slo.test.ts.snap index 00dc9bb4654ae..90690a4989586 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/__snapshots__/reset_slo.test.ts.snap +++ b/x-pack/plugins/observability_solution/slo/server/services/__snapshots__/reset_slo.test.ts.snap @@ -208,6 +208,12 @@ exports[`ResetSLO resets all associated resources 8`] = ` "description": "Ingest pipeline for SLO rollup data [id: irrelevant, revision: 1]", "id": ".slo-observability.sli.pipeline-irrelevant-1", "processors": Array [ + Object { + "set": Object { + "field": "_id", + "value": "{{{_id}}}-irrelevant-1", + }, + }, Object { "set": Object { "field": "event.ingested", diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/synthetics_availability.test.ts.snap b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/synthetics_availability.test.ts.snap new file mode 100644 index 0000000000000..3c71844678885 --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/synthetics_availability.test.ts.snap @@ -0,0 +1,117 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Synthetics Availability Transform Generator returns the expected transform params 1`] = ` +Object { + "_meta": Object { + "managed": true, + "managed_by": "observability", + "version": 3.3, + }, + "defer_validation": true, + "description": "Rolled-up SLI data for SLO: irrelevant [id: irrelevant, revision: 1]", + "dest": Object { + "index": ".slo-observability.sli-v3.3", + "pipeline": ".slo-observability.sli.pipeline-irrelevant-1", + }, + "frequency": "1m", + "pivot": Object { + "aggregations": Object { + "slo.denominator": Object { + "filter": Object { + "term": Object { + "summary.final_attempt": true, + }, + }, + }, + "slo.numerator": Object { + "filter": Object { + "term": Object { + "monitor.status": "up", + }, + }, + }, + }, + "group_by": Object { + "@timestamp": Object { + "date_histogram": Object { + "field": "@timestamp", + "fixed_interval": "1m", + }, + }, + "monitor.config_id": Object { + "terms": Object { + "field": "config_id", + }, + }, + "monitor.name": Object { + "terms": Object { + "field": "monitor.name", + }, + }, + "observer.geo.name": Object { + "terms": Object { + "field": "observer.geo.name", + }, + }, + "observer.name": Object { + "terms": Object { + "field": "observer.name", + }, + }, + "slo.groupings.monitor.id": Object { + "terms": Object { + "field": "monitor.id", + }, + }, + "slo.groupings.monitor.name": Object { + "terms": Object { + "field": "monitor.name", + }, + }, + "slo.groupings.observer.geo.name": Object { + "terms": Object { + "field": "observer.geo.name", + }, + }, + }, + }, + "settings": Object { + "deduce_mappings": false, + "unattended": true, + }, + "source": Object { + "index": "synthetics-*", + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "term": Object { + "summary.final_attempt": true, + }, + }, + Object { + "term": Object { + "meta.space_id": "custom-space", + }, + }, + Object { + "range": Object { + "@timestamp": Object { + "gte": "now-7d/d", + }, + }, + }, + ], + }, + }, + "runtime_mappings": Object {}, + }, + "sync": Object { + "time": Object { + "delay": "1m", + "field": "event.ingested", + }, + }, + "transform_id": "slo-irrelevant-1", +} +`; diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/transform_generator.test.ts.snap b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/transform_generator.test.ts.snap new file mode 100644 index 0000000000000..144a4fa35eda5 --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/transform_generator.test.ts.snap @@ -0,0 +1,73 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Transform Generator builds common runtime mappings and group by with single group by 1`] = `Object {}`; + +exports[`Transform Generator builds common runtime mappings and group by with single group by 2`] = ` +Object { + "@timestamp": Object { + "date_histogram": Object { + "field": "@timestamp", + "fixed_interval": "1m", + }, + }, + "slo.groupings.example": Object { + "terms": Object { + "field": "example", + }, + }, +} +`; + +exports[`Transform Generator builds common runtime mappings and group by with single group by 3`] = `Object {}`; + +exports[`Transform Generator builds common runtime mappings and group by with single group by 4`] = ` +Object { + "@timestamp": Object { + "date_histogram": Object { + "field": "@timestamp", + "fixed_interval": "1m", + }, + }, + "slo.groupings.example": Object { + "terms": Object { + "field": "example", + }, + }, +} +`; + +exports[`Transform Generator builds common runtime mappings without multi group by 1`] = `Object {}`; + +exports[`Transform Generator builds common runtime mappings without multi group by 2`] = ` +Object { + "@timestamp": Object { + "date_histogram": Object { + "field": "@timestamp", + "fixed_interval": "1m", + }, + }, + "slo.groupings.example1": Object { + "terms": Object { + "field": "example1", + }, + }, + "slo.groupings.example2": Object { + "terms": Object { + "field": "example2", + }, + }, +} +`; + +exports[`Transform Generator builds empty runtime mappings without group by 1`] = `Object {}`; + +exports[`Transform Generator builds empty runtime mappings without group by 2`] = ` +Object { + "@timestamp": Object { + "date_histogram": Object { + "field": "@timestamp", + "fixed_interval": "1m", + }, + }, +} +`; 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 f9caeb9f57c31..565a0d56d1ff4 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 @@ -5,13 +5,12 @@ * 2.0. */ -import { ALL_VALUE } from '@kbn/slo-schema'; import { dataViewsService } from '@kbn/data-views-plugin/server/mocks'; +import { ALL_VALUE } from '@kbn/slo-schema'; import { SLODefinition } from '../../domain/models'; +import { twoMinute } from '../fixtures/duration'; import { createSLO, createSyntheticsAvailabilityIndicator } from '../fixtures/slo'; import { SyntheticsAvailabilityTransformGenerator } from './synthetics_availability'; -import { SYNTHETICS_INDEX_PATTERN } from '../../../common/constants'; -import { twoMinute } from '../fixtures/duration'; const generator = new SyntheticsAvailabilityTransformGenerator(); @@ -22,119 +21,7 @@ describe('Synthetics Availability Transform Generator', () => { const slo = createSLO({ id: 'irrelevant', indicator: createSyntheticsAvailabilityIndicator() }); const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); - expect(transform).toEqual({ - _meta: { - managed: true, - managed_by: 'observability', - version: 3.3, - }, - defer_validation: true, - description: 'Rolled-up SLI data for SLO: irrelevant [id: irrelevant, revision: 1]', - dest: { - index: '.slo-observability.sli-v3.3', - pipeline: '.slo-observability.sli.pipeline-irrelevant-1', - }, - frequency: '1m', - pivot: { - aggregations: { - 'slo.denominator': { - filter: { - term: { - 'summary.final_attempt': true, - }, - }, - }, - 'slo.numerator': { - filter: { - term: { - 'monitor.status': 'up', - }, - }, - }, - }, - group_by: { - '@timestamp': { - date_histogram: { - field: '@timestamp', - fixed_interval: '1m', - }, - }, - 'monitor.config_id': { - terms: { - field: 'config_id', - }, - }, - 'monitor.name': { - terms: { - field: 'monitor.name', - }, - }, - 'observer.name': { - terms: { - field: 'observer.name', - }, - }, - 'observer.geo.name': { - terms: { - field: 'observer.geo.name', - }, - }, - 'slo.groupings.monitor.name': { - terms: { - field: 'monitor.name', - }, - }, - 'slo.groupings.observer.geo.name': { - terms: { - field: 'observer.geo.name', - }, - }, - 'slo.groupings.monitor.id': { - terms: { - field: 'monitor.id', - }, - }, - }, - }, - settings: { - deduce_mappings: false, - unattended: true, - }, - source: { - index: SYNTHETICS_INDEX_PATTERN, - query: { - bool: { - filter: [ - { - term: { - 'summary.final_attempt': true, - }, - }, - { - term: { - 'meta.space_id': 'custom-space', - }, - }, - { - range: { - '@timestamp': { - gte: 'now-7d/d', - }, - }, - }, - ], - }, - }, - runtime_mappings: {}, - }, - sync: { - time: { - delay: '1m', - field: 'event.ingested', - }, - }, - transform_id: 'slo-irrelevant-1', - }); + expect(transform).toMatchSnapshot(); expect(transform.source.query?.bool?.filter).toContainEqual({ term: { 'summary.final_attempt': true, diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/transform_generator.test.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/transform_generator.test.ts index ffb165fdb4326..9f07c6cfb5afa 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/transform_generator.test.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/transform_generator.test.ts @@ -17,19 +17,10 @@ describe('Transform Generator', () => { indicator: createAPMTransactionErrorRateIndicator(), }); const commonRuntime = generator.buildCommonRuntimeMappings(slo); - - expect(commonRuntime).toEqual({}); + expect(commonRuntime).toMatchSnapshot(); const commonGroupBy = generator.buildCommonGroupBy(slo); - - expect(commonGroupBy).toEqual({ - '@timestamp': { - date_histogram: { - field: '@timestamp', - fixed_interval: '1m', - }, - }, - }); + expect(commonGroupBy).toMatchSnapshot(); }); it.each(['example', ['example']])( @@ -42,24 +33,10 @@ describe('Transform Generator', () => { indicator, }); const commonRuntime = generator.buildCommonRuntimeMappings(slo); - - expect(commonRuntime).toEqual({}); + expect(commonRuntime).toMatchSnapshot(); const commonGroupBy = generator.buildCommonGroupBy(slo); - - expect(commonGroupBy).toEqual({ - '@timestamp': { - date_histogram: { - field: '@timestamp', - fixed_interval: '1m', - }, - }, - 'slo.groupings.example': { - terms: { - field: 'example', - }, - }, - }); + expect(commonGroupBy).toMatchSnapshot(); } ); @@ -71,28 +48,9 @@ describe('Transform Generator', () => { indicator, }); const commonRuntime = generator.buildCommonRuntimeMappings(slo); - - expect(commonRuntime).toEqual({}); + expect(commonRuntime).toMatchSnapshot(); const commonGroupBy = generator.buildCommonGroupBy(slo); - - expect(commonGroupBy).toEqual({ - '@timestamp': { - date_histogram: { - field: '@timestamp', - fixed_interval: '1m', - }, - }, - 'slo.groupings.example1': { - terms: { - field: 'example1', - }, - }, - 'slo.groupings.example2': { - terms: { - field: 'example2', - }, - }, - }); + expect(commonGroupBy).toMatchSnapshot(); }); });