diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index 6ac44dedc0e33..7584f145c8945 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -49743,6 +49743,48 @@ paths: summary: Create an SLO tags: - slo + /s/{spaceId}/api/observability/slos/_bulk_purge_rollup: + post: + description: | + The deletion occurs for the specified list of `sloId`. You must have `all` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges. + operationId: deleteRollupDataOp + parameters: + - $ref: '#/components/parameters/SLOs_kbn_xsrf' + - $ref: '#/components/parameters/SLOs_space_id' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SLOs_bulk_purge_rollup_request' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/SLOs_bulk_purge_rollup_response' + description: Successful request + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/SLOs_400_response' + description: Bad request + '401': + content: + application/json: + schema: + $ref: '#/components/schemas/SLOs_401_response' + description: Unauthorized response + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/SLOs_403_response' + description: Unauthorized response + summary: Batch delete rollup and summary data + tags: + - slo /s/{spaceId}/api/observability/slos/_delete_instances: post: description: | @@ -50051,7 +50093,7 @@ paths: name: includeOutdatedOnly schema: type: boolean - - description: Specify which SLO tags to query by (comma-separated list) + - description: Filters the SLOs by tag in: query name: tags schema: @@ -65575,6 +65617,58 @@ components: example: occurrences title: Budgeting method type: string + SLOs_bulk_purge_rollup_request: + description: | + The bulk purge rollup data request takes a list of SLO ids and a purge policy, then deletes the rollup data according to the purge policy. This API can be used to remove the staled data of an instance SLO that no longer get updated. + properties: + list: + description: An array of slo ids + items: + description: The SLO Definition id + example: 8853df00-ae2e-11ed-90af-09bb6422b258 + type: string + type: array + purgePolicy: + description: Policy that dictates which SLI documents to purge based on age + oneOf: + - type: object + properties: + age: + description: The duration to determine which documents to purge, formatted as {duration}{unit}. This value should be greater than or equal to the time window of every SLO provided. + example: 7d + type: string + purgeType: + description: Specifies whether documents will be purged based on a specific age or on a timestamp + enum: + - fixed-age + type: string + - type: object + properties: + purgeType: + description: Specifies whether documents will be purged based on a specific age or on a timestamp + enum: + - fixed-time + type: string + timestamp: + description: The timestamp to determine which documents to purge, formatted in ISO. This value should be older than the applicable time window of every SLO provided. + example: '2024-12-31T00:00:00.000Z' + type: string + type: object + required: + - list + - purgePolicy + title: Bulk Purge Rollup data request + type: object + SLOs_bulk_purge_rollup_response: + description: | + The bulk purge rollup data response returns a task id from the elasticsearch deleteByQuery response. + properties: + taskId: + description: The task id of the purge operation + example: 8853df00-ae2e-11ed-90af-09bb6422b258 + type: string + title: Bulk Purge Rollup data response + type: object SLOs_create_slo_request: description: | The create SLO API request body varies depending on the type of indicator, time window and budgeting method. diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index 54a56cce1402c..6bbd46ad51b17 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -54383,6 +54383,48 @@ paths: summary: Create an SLO tags: - slo + /s/{spaceId}/api/observability/slos/_bulk_purge_rollup: + post: + description: | + The deletion occurs for the specified list of `sloId`. You must have `all` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges. + operationId: deleteRollupDataOp + parameters: + - $ref: '#/components/parameters/SLOs_kbn_xsrf' + - $ref: '#/components/parameters/SLOs_space_id' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SLOs_bulk_purge_rollup_request' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/SLOs_bulk_purge_rollup_response' + description: Successful request + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/SLOs_400_response' + description: Bad request + '401': + content: + application/json: + schema: + $ref: '#/components/schemas/SLOs_401_response' + description: Unauthorized response + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/SLOs_403_response' + description: Unauthorized response + summary: Batch delete rollup and summary data + tags: + - slo /s/{spaceId}/api/observability/slos/_delete_instances: post: description: | @@ -54691,7 +54733,7 @@ paths: name: includeOutdatedOnly schema: type: boolean - - description: Specify which SLO tags to query by (comma-separated list) + - description: Filters the SLOs by tag in: query name: tags schema: @@ -75088,6 +75130,58 @@ components: example: occurrences title: Budgeting method type: string + SLOs_bulk_purge_rollup_request: + description: | + The bulk purge rollup data request takes a list of SLO ids and a purge policy, then deletes the rollup data according to the purge policy. This API can be used to remove the staled data of an instance SLO that no longer get updated. + properties: + list: + description: An array of slo ids + items: + description: The SLO Definition id + example: 8853df00-ae2e-11ed-90af-09bb6422b258 + type: string + type: array + purgePolicy: + description: Policy that dictates which SLI documents to purge based on age + oneOf: + - type: object + properties: + age: + description: The duration to determine which documents to purge, formatted as {duration}{unit}. This value should be greater than or equal to the time window of every SLO provided. + example: 7d + type: string + purgeType: + description: Specifies whether documents will be purged based on a specific age or on a timestamp + enum: + - fixed-age + type: string + - type: object + properties: + purgeType: + description: Specifies whether documents will be purged based on a specific age or on a timestamp + enum: + - fixed-time + type: string + timestamp: + description: The timestamp to determine which documents to purge, formatted in ISO. This value should be older than the applicable time window of every SLO provided. + example: '2024-12-31T00:00:00.000Z' + type: string + type: object + required: + - list + - purgePolicy + title: Bulk Purge Rollup data request + type: object + SLOs_bulk_purge_rollup_response: + description: | + The bulk purge rollup data response returns a task id from the elasticsearch deleteByQuery response. + properties: + taskId: + description: The task id of the purge operation + example: 8853df00-ae2e-11ed-90af-09bb6422b258 + type: string + title: Bulk Purge Rollup data response + type: object SLOs_create_slo_request: description: | The create SLO API request body varies depending on the type of indicator, time window and budgeting method. diff --git a/x-pack/platform/packages/shared/kbn-slo-schema/src/rest_specs/routes/bulk_purge_rollup.ts b/x-pack/platform/packages/shared/kbn-slo-schema/src/rest_specs/routes/bulk_purge_rollup.ts new file mode 100644 index 0000000000000..7764a20bf6ef6 --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-slo-schema/src/rest_specs/routes/bulk_purge_rollup.ts @@ -0,0 +1,46 @@ +/* + * 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 * as t from 'io-ts'; +import { DeleteByQueryResponse } from '@elastic/elasticsearch/lib/api/types'; +import { dateType, durationType } from '../../schema'; + +const fixedAgePurgeVal = t.literal('fixed_age'); +const fixedTimePurgeVal = t.literal('fixed_time'); + +const fixedAgePurge = t.type({ + purgeType: fixedAgePurgeVal, + age: durationType, +}); + +const fixedTimePurge = t.type({ + purgeType: fixedTimePurgeVal, + timestamp: dateType, +}); + +const bulkPurgePolicy = t.union([fixedAgePurge, fixedTimePurge]); + +const bulkPurgeRollupSchema = t.type({ + body: t.intersection([ + t.type({ + list: t.array(t.string), + purgePolicy: bulkPurgePolicy, + }), + t.partial({ + force: t.boolean, + }), + ]), +}); + +interface BulkPurgeRollupResponse { + taskId?: DeleteByQueryResponse['task']; +} + +type BulkPurgePolicyType = t.TypeOf; +type BulkPurgeRollupParams = t.TypeOf; + +export type { BulkPurgeRollupResponse, BulkPurgePolicyType, BulkPurgeRollupParams }; +export { bulkPurgeRollupSchema }; diff --git a/x-pack/platform/packages/shared/kbn-slo-schema/src/rest_specs/routes/index.ts b/x-pack/platform/packages/shared/kbn-slo-schema/src/rest_specs/routes/index.ts index ea7f23926134e..408d11fb2ed10 100644 --- a/x-pack/platform/packages/shared/kbn-slo-schema/src/rest_specs/routes/index.ts +++ b/x-pack/platform/packages/shared/kbn-slo-schema/src/rest_specs/routes/index.ts @@ -13,6 +13,7 @@ export * from './find_group'; export * from './find_definition'; export * from './get'; export * from './get_burn_rates'; +export * from './bulk_purge_rollup'; export * from './get_slo_groupings'; export * from './get_slo_stats_overview'; export * from './get_preview_data'; diff --git a/x-pack/solutions/observability/plugins/slo/docs/openapi/slo/bundled.json b/x-pack/solutions/observability/plugins/slo/docs/openapi/slo/bundled.json index ffe718f1db21a..d4bbb3e434794 100644 --- a/x-pack/solutions/observability/plugins/slo/docs/openapi/slo/bundled.json +++ b/x-pack/solutions/observability/plugins/slo/docs/openapi/slo/bundled.json @@ -698,6 +698,76 @@ } } }, + "/s/{spaceId}/api/observability/slos/_bulk_purge_rollup": { + "post": { + "summary": "Batch delete rollup and summary data", + "operationId": "deleteRollupDataOp", + "description": "The deletion occurs for the specified list of `sloId`. You must have `all` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges.\n", + "tags": [ + "slo" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/space_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bulk_purge_rollup_request" + } + } + } + }, + "responses": { + "200": { + "description": "Successful request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bulk_purge_rollup_response" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + }, + "401": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401_response" + } + } + } + }, + "403": { + "description": "Unauthorized response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403_response" + } + } + } + } + } + } + }, "/s/{spaceId}/internal/observability/slos/_definitions": { "get": { "summary": "Get the SLO definitions", @@ -725,11 +795,10 @@ { "name": "tags", "in": "query", - "description": "Specify which SLO tags to query by (comma-separated values)", + "description": "Filters the SLOs by tag", "schema": { "type": "string" - }, - "example": true + } }, { "name": "search", @@ -2492,6 +2561,78 @@ } } }, + "bulk_purge_rollup_request": { + "title": "Bulk Purge Rollup data request", + "description": "The bulk purge rollup data request takes a list of SLO ids and a purge policy, then deletes the rollup data according to the purge policy. This API can be used to remove the staled data of an instance SLO that no longer get updated.\n", + "type": "object", + "required": [ + "list", + "purgePolicy" + ], + "properties": { + "list": { + "description": "An array of slo ids", + "type": "array", + "items": { + "type": "string", + "description": "The SLO Definition id", + "example": "8853df00-ae2e-11ed-90af-09bb6422b258" + } + }, + "purgePolicy": { + "description": "Policy that dictates which SLI documents to purge based on age", + "type": "object", + "oneOf": [ + { + "type": "object", + "properties": { + "purgeType": { + "type": "string", + "description": "Specifies whether documents will be purged based on a specific age or on a timestamp", + "enum": [ + "fixed-age" + ] + }, + "age": { + "type": "string", + "description": "The duration to determine which documents to purge, formatted as {duration}{unit}. This value should be greater than or equal to the time window of every SLO provided.", + "example": "7d" + } + } + }, + { + "type": "object", + "properties": { + "purgeType": { + "type": "string", + "description": "Specifies whether documents will be purged based on a specific age or on a timestamp", + "enum": [ + "fixed-time" + ] + }, + "timestamp": { + "type": "string", + "description": "The timestamp to determine which documents to purge, formatted in ISO. This value should be older than the applicable time window of every SLO provided.", + "example": "2024-12-31T00:00:00.000Z" + } + } + } + ] + } + } + }, + "bulk_purge_rollup_response": { + "title": "Bulk Purge Rollup data response", + "description": "The bulk purge rollup data response returns a task id from the elasticsearch deleteByQuery response.\n", + "type": "object", + "properties": { + "taskId": { + "type": "string", + "description": "The task id of the purge operation", + "example": "8853df00-ae2e-11ed-90af-09bb6422b258" + } + } + }, "find_slo_definitions_response": { "title": "Find SLO definitions response", "description": "A paginated response of SLO definitions matching the query.\n", diff --git a/x-pack/solutions/observability/plugins/slo/docs/openapi/slo/bundled.yaml b/x-pack/solutions/observability/plugins/slo/docs/openapi/slo/bundled.yaml index 80d15bf2e85d6..1068d11a80856 100644 --- a/x-pack/solutions/observability/plugins/slo/docs/openapi/slo/bundled.yaml +++ b/x-pack/solutions/observability/plugins/slo/docs/openapi/slo/bundled.yaml @@ -424,6 +424,48 @@ paths: application/json: schema: $ref: '#/components/schemas/404_response' + /s/{spaceId}/api/observability/slos/_bulk_purge_rollup: + post: + summary: Batch delete rollup and summary data + operationId: deleteRollupDataOp + description: | + The deletion occurs for the specified list of `sloId`. You must have `all` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges. + tags: + - slo + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/components/parameters/space_id' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/bulk_purge_rollup_request' + responses: + '200': + description: Successful request + content: + application/json: + schema: + $ref: '#/components/schemas/bulk_purge_rollup_response' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/400_response' + '401': + description: Unauthorized response + content: + application/json: + schema: + $ref: '#/components/schemas/401_response' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '#/components/schemas/403_response' /s/{spaceId}/internal/observability/slos/_definitions: get: summary: Get the SLO definitions @@ -443,7 +485,7 @@ paths: example: true - name: tags in: query - description: 'Specify which SLO tags to query by (comma-separated list)' + description: Filters the SLOs by tag schema: type: string - name: search @@ -1475,7 +1517,7 @@ components: example: Bad Request message: type: string - example: "Invalid value 'foo' supplied to: [...]" + example: 'Invalid value ''foo'' supplied to: [...]' 401_response: title: Unauthorized type: object @@ -1718,6 +1760,58 @@ components: description: The internal SLO version type: number example: 2 + bulk_purge_rollup_request: + title: Bulk Purge Rollup data request + description: | + The bulk purge rollup data request takes a list of SLO ids and a purge policy, then deletes the rollup data according to the purge policy. This API can be used to remove the staled data of an instance SLO that no longer get updated. + type: object + required: + - list + - purgePolicy + properties: + list: + description: An array of slo ids + type: array + items: + type: string + description: The SLO Definition id + example: 8853df00-ae2e-11ed-90af-09bb6422b258 + purgePolicy: + description: Policy that dictates which SLI documents to purge based on age + type: object + oneOf: + - type: object + properties: + purgeType: + type: string + description: Specifies whether documents will be purged based on a specific age or on a timestamp + enum: + - fixed-age + age: + type: string + description: The duration to determine which documents to purge, formatted as {duration}{unit}. This value should be greater than or equal to the time window of every SLO provided. + example: 7d + - type: object + properties: + purgeType: + type: string + description: Specifies whether documents will be purged based on a specific age or on a timestamp + enum: + - fixed-time + timestamp: + type: string + description: The timestamp to determine which documents to purge, formatted in ISO. This value should be older than the applicable time window of every SLO provided. + example: '2024-12-31T00:00:00.000Z' + bulk_purge_rollup_response: + title: Bulk Purge Rollup data response + description: | + The bulk purge rollup data response returns a task id from the elasticsearch deleteByQuery response. + type: object + properties: + taskId: + type: string + description: The task id of the purge operation + example: 8853df00-ae2e-11ed-90af-09bb6422b258 find_slo_definitions_response: title: Find SLO definitions response description: | diff --git a/x-pack/solutions/observability/plugins/slo/docs/openapi/slo/components/schemas/bulk_purge_rollup_request.yaml b/x-pack/solutions/observability/plugins/slo/docs/openapi/slo/components/schemas/bulk_purge_rollup_request.yaml new file mode 100644 index 0000000000000..c431b13f70bbe --- /dev/null +++ b/x-pack/solutions/observability/plugins/slo/docs/openapi/slo/components/schemas/bulk_purge_rollup_request.yaml @@ -0,0 +1,42 @@ +title: Bulk Purge Rollup data request +description: > + The bulk purge rollup data request takes a list of SLO ids and a purge policy, then deletes the rollup data according to the purge policy. + This API can be used to remove the staled data of an instance SLO that no longer get updated. +type: object +required: + - list + - purgePolicy +properties: + list: + description: An array of slo ids + type: array + items: + type: string + description: The SLO Definition id + example: 8853df00-ae2e-11ed-90af-09bb6422b258 + purgePolicy: + description: Policy that dictates which SLI documents to purge based on age + type: object + oneOf: + - type: object + properties: + purgeType: + type: string + description: Specifies whether documents will be purged based on a specific age or on a timestamp + enum: + - fixed-age + age: + type: string + description: The duration to determine which documents to purge, formatted as {duration}{unit}. This value should be greater than or equal to the time window of every SLO provided. + example: 7d + - type: object + properties: + purgeType: + type: string + description: Specifies whether documents will be purged based on a specific age or on a timestamp + enum: + - fixed-time + timestamp: + type: string + description: The timestamp to determine which documents to purge, formatted in ISO. This value should be older than the applicable time window of every SLO provided. + example: 2024-12-31T00:00:00.000Z diff --git a/x-pack/solutions/observability/plugins/slo/docs/openapi/slo/components/schemas/bulk_purge_rollup_response.yaml b/x-pack/solutions/observability/plugins/slo/docs/openapi/slo/components/schemas/bulk_purge_rollup_response.yaml new file mode 100644 index 0000000000000..4b7f71061e94a --- /dev/null +++ b/x-pack/solutions/observability/plugins/slo/docs/openapi/slo/components/schemas/bulk_purge_rollup_response.yaml @@ -0,0 +1,9 @@ +title: Bulk Purge Rollup data response +description: > + The bulk purge rollup data response returns a task id from the elasticsearch deleteByQuery response. +type: object +properties: + taskId: + type: string + description: The task id of the purge operation + example: 8853df00-ae2e-11ed-90af-09bb6422b258 diff --git a/x-pack/solutions/observability/plugins/slo/docs/openapi/slo/entrypoint.yaml b/x-pack/solutions/observability/plugins/slo/docs/openapi/slo/entrypoint.yaml index 578bf1ffbfceb..71dabe8e8d24e 100644 --- a/x-pack/solutions/observability/plugins/slo/docs/openapi/slo/entrypoint.yaml +++ b/x-pack/solutions/observability/plugins/slo/docs/openapi/slo/entrypoint.yaml @@ -27,6 +27,8 @@ paths: $ref: 'paths/s@{spaceid}@api@slos@{sloid}@disable.yaml' '/s/{spaceId}/api/observability/slos/{sloId}/_reset': $ref: 'paths/s@{spaceid}@api@slos@{sloid}@_reset.yaml' + '/s/{spaceId}/api/observability/slos/_bulk_purge_rollup': + $ref: 'paths/s@{spaceid}@api@slos@_bulk_purge_rollup.yaml' # "/s/{spaceId}/internal/observability/slos/_historical_summary": # $ref: "paths/s@{spaceid}@api@slos@_historical_summary.yaml" '/s/{spaceId}/internal/observability/slos/_definitions': diff --git a/x-pack/solutions/observability/plugins/slo/docs/openapi/slo/paths/s@{spaceid}@api@slos@_bulk_purge_rollup.yaml b/x-pack/solutions/observability/plugins/slo/docs/openapi/slo/paths/s@{spaceid}@api@slos@_bulk_purge_rollup.yaml new file mode 100644 index 0000000000000..9b62f3894d32f --- /dev/null +++ b/x-pack/solutions/observability/plugins/slo/docs/openapi/slo/paths/s@{spaceid}@api@slos@_bulk_purge_rollup.yaml @@ -0,0 +1,43 @@ +post: + summary: Batch delete rollup and summary data + operationId: deleteRollupDataOp + description: > + The deletion occurs for the specified list of `sloId`. + You must have `all` privileges for the **SLOs** feature in the + **Observability** section of the Kibana feature privileges. + tags: + - slo + parameters: + - $ref: ../components/headers/kbn_xsrf.yaml + - $ref: ../components/parameters/space_id.yaml + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/bulk_purge_rollup_request.yaml' + responses: + '200': + description: Successful request + content: + application/json: + schema: + $ref: '../components/schemas/bulk_purge_rollup_response.yaml' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '../components/schemas/400_response.yaml' + '401': + description: Unauthorized response + content: + application/json: + schema: + $ref: '../components/schemas/401_response.yaml' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '../components/schemas/403_response.yaml' diff --git a/x-pack/solutions/observability/plugins/slo/server/routes/slo/purge_rollup_data.ts b/x-pack/solutions/observability/plugins/slo/server/routes/slo/purge_rollup_data.ts new file mode 100644 index 0000000000000..6031a0d7cdb24 --- /dev/null +++ b/x-pack/solutions/observability/plugins/slo/server/routes/slo/purge_rollup_data.ts @@ -0,0 +1,32 @@ +/* + * 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 { bulkPurgeRollupSchema } from '@kbn/slo-schema'; +import { createSloServerRoute } from '../create_slo_server_route'; +import { assertPlatinumLicense } from './utils/assert_platinum_license'; +import { BulkPurgeRollupData } from '../../services/bulk_purge_rollup_data'; + +export const bulkPurgeRollupRoute = createSloServerRoute({ + endpoint: 'POST /api/observability/slos/_bulk_purge_rollup 2023-10-31', + security: { + authz: { + requiredPrivileges: ['slo_write'], + }, + }, + params: bulkPurgeRollupSchema, + handler: async ({ request, context, params, logger, plugins, getScopedClients }) => { + await assertPlatinumLicense(plugins); + + const { repository } = await getScopedClients({ request, logger }); + + const core = await context.core; + const esClient = core.elasticsearch.client.asCurrentUser; + const purgeRollupData = new BulkPurgeRollupData(esClient, repository); + + return purgeRollupData.execute(params.body); + }, +}); diff --git a/x-pack/solutions/observability/plugins/slo/server/routes/slo/route.ts b/x-pack/solutions/observability/plugins/slo/server/routes/slo/route.ts index 901c8a3e689e2..a800c45af409d 100644 --- a/x-pack/solutions/observability/plugins/slo/server/routes/slo/route.ts +++ b/x-pack/solutions/observability/plugins/slo/server/routes/slo/route.ts @@ -27,6 +27,7 @@ import { getSLOSuggestionsRoute } from './get_suggestions'; import { putSloSettings } from './put_slo_settings'; import { resetSLORoute } from './reset_slo'; import { getSLOStatsOverview } from './get_slo_stats_overview'; +import { bulkPurgeRollupRoute } from './purge_rollup_data'; export const getSloRouteRepository = (isServerless?: boolean) => { return { @@ -37,6 +38,7 @@ export const getSloRouteRepository = (isServerless?: boolean) => { ...inspectSLORoute, ...deleteSLORoute, ...deleteSloInstancesRoute, + ...bulkPurgeRollupRoute, ...disableSLORoute, ...enableSLORoute, ...fetchHistoricalSummary, diff --git a/x-pack/solutions/observability/plugins/slo/server/services/__snapshots__/purge_rollup_data.test.ts.snap b/x-pack/solutions/observability/plugins/slo/server/services/__snapshots__/purge_rollup_data.test.ts.snap new file mode 100644 index 0000000000000..b5fce66e5b0de --- /dev/null +++ b/x-pack/solutions/observability/plugins/slo/server/services/__snapshots__/purge_rollup_data.test.ts.snap @@ -0,0 +1,247 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`purge rollup data happy path successfully makes a forced query to remove recently added SLI data 1`] = ` +[MockFunction] { + "calls": Array [ + Array [ + Object { + "conflicts": "proceed", + "index": ".slo-observability.sli-v3*", + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "terms": Object { + "slo.id": Array [ + "test5", + ], + }, + }, + Object { + "range": Object { + "@timestamp": Object { + "lte": "now-1d", + }, + }, + }, + ], + }, + }, + "refresh": false, + "slices": "auto", + "wait_for_completion": false, + }, + ], + ], + "results": Array [ + Object { + "type": "return", + "value": Promise {}, + }, + ], +} +`; + +exports[`purge rollup data happy path successfully makes a query to remove SLI data based on a timestamp - month 1`] = ` +[MockFunction] { + "calls": Array [ + Array [ + Object { + "conflicts": "proceed", + "index": ".slo-observability.sli-v3*", + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "terms": Object { + "slo.id": Array [ + "test3", + ], + }, + }, + Object { + "range": Object { + "@timestamp": Object { + "lte": "2025-03-01T00:00:00.000Z", + }, + }, + }, + ], + }, + }, + "refresh": false, + "slices": "auto", + "wait_for_completion": false, + }, + ], + ], + "results": Array [ + Object { + "type": "return", + "value": Promise {}, + }, + ], +} +`; + +exports[`purge rollup data happy path successfully makes a query to remove SLI data based on a timestamp - week 1`] = ` +[MockFunction] { + "calls": Array [ + Array [ + Object { + "conflicts": "proceed", + "index": ".slo-observability.sli-v3*", + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "terms": Object { + "slo.id": Array [ + "test4", + ], + }, + }, + Object { + "range": Object { + "@timestamp": Object { + "lte": "2025-04-01T00:00:00.000Z", + }, + }, + }, + ], + }, + }, + "refresh": false, + "slices": "auto", + "wait_for_completion": false, + }, + ], + ], + "results": Array [ + Object { + "type": "return", + "value": Promise {}, + }, + ], +} +`; + +exports[`purge rollup data happy path successfully makes a query to remove SLI data older than 30 days 1`] = ` +[MockFunction] { + "calls": Array [ + Array [ + Array [ + "test1", + ], + ], + ], + "results": Array [ + Object { + "type": "return", + "value": Promise {}, + }, + ], +} +`; + +exports[`purge rollup data happy path successfully makes a query to remove SLI data older than 30 days 2`] = ` +[MockFunction] { + "calls": Array [ + Array [ + Object { + "conflicts": "proceed", + "index": ".slo-observability.sli-v3*", + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "terms": Object { + "slo.id": Array [ + "test1", + ], + }, + }, + Object { + "range": Object { + "@timestamp": Object { + "lte": "now-30d", + }, + }, + }, + ], + }, + }, + "refresh": false, + "slices": "auto", + "wait_for_completion": false, + }, + ], + ], + "results": Array [ + Object { + "type": "return", + "value": Promise {}, + }, + ], +} +`; + +exports[`purge rollup data happy path successfully makes a query to remove SLI data older than a week 1`] = ` +[MockFunction] { + "calls": Array [ + Array [ + Array [ + "test2", + ], + ], + ], + "results": Array [ + Object { + "type": "return", + "value": Promise {}, + }, + ], +} +`; + +exports[`purge rollup data happy path successfully makes a query to remove SLI data older than a week 2`] = ` +[MockFunction] { + "calls": Array [ + Array [ + Object { + "conflicts": "proceed", + "index": ".slo-observability.sli-v3*", + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "terms": Object { + "slo.id": Array [ + "test2", + ], + }, + }, + Object { + "range": Object { + "@timestamp": Object { + "lte": "now-2w", + }, + }, + }, + ], + }, + }, + "refresh": false, + "slices": "auto", + "wait_for_completion": false, + }, + ], + ], + "results": Array [ + Object { + "type": "return", + "value": Promise {}, + }, + ], +} +`; diff --git a/x-pack/solutions/observability/plugins/slo/server/services/bulk_purge_rollup_data.ts b/x-pack/solutions/observability/plugins/slo/server/services/bulk_purge_rollup_data.ts new file mode 100644 index 0000000000000..b58f1cda4efdb --- /dev/null +++ b/x-pack/solutions/observability/plugins/slo/server/services/bulk_purge_rollup_data.ts @@ -0,0 +1,112 @@ +/* + * 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 { ElasticsearchClient } from '@kbn/core/server'; +import { + BulkPurgeRollupResponse, + BulkPurgeRollupParams, +} from '@kbn/slo-schema/src/rest_specs/routes/bulk_purge_rollup'; +import { calendarAlignedTimeWindowSchema } from '@kbn/slo-schema'; +import { assertNever } from '@elastic/eui'; +import moment from 'moment'; +import { SLI_DESTINATION_INDEX_PATTERN } from '../../common/constants'; +import { SLORepository } from './slo_repository'; +import { IllegalArgumentError } from '../errors'; +import { SLODefinition } from '../domain/models'; + +export class BulkPurgeRollupData { + constructor(private esClient: ElasticsearchClient, private repository: SLORepository) {} + + public async execute(params: BulkPurgeRollupParams): Promise { + const lookback = this.getTimestamp(params.purgePolicy); + const slos = await this.repository.findAllByIds(params.list); + + if (params.force !== true) { + await this.validatePurgePolicy(slos, params.purgePolicy); + } + + const response = await this.esClient.deleteByQuery({ + index: SLI_DESTINATION_INDEX_PATTERN, + refresh: false, + wait_for_completion: false, + conflicts: 'proceed', + slices: 'auto', + query: { + bool: { + filter: [ + { + terms: { 'slo.id': slos.map((slo) => slo.id) }, + }, + { + range: { + '@timestamp': { + lte: lookback, + }, + }, + }, + ], + }, + }, + }); + + return { taskId: response.task }; + } + + private async validatePurgePolicy( + slos: SLODefinition[], + purgePolicy: BulkPurgeRollupParams['purgePolicy'] + ) { + const purgeType = purgePolicy.purgeType; + let inputIsInvalid = false; + + switch (purgeType) { + case 'fixed_age': + const duration = purgePolicy.age; + inputIsInvalid = slos.some((slo) => { + if (calendarAlignedTimeWindowSchema.is(slo.timeWindow)) { + return moment(Date.now()) + .subtract(duration.asSeconds(), 's') + .isAfter(moment(Date.now()).startOf(slo.timeWindow.duration.unit)); + } else { + return duration.isShorterThan(slo.timeWindow.duration); + } + }); + break; + case 'fixed_time': + const timestampMoment = moment(purgePolicy.timestamp); + inputIsInvalid = slos.some((slo) => { + if (calendarAlignedTimeWindowSchema.is(slo.timeWindow)) { + return timestampMoment.isAfter( + moment(Date.now()).startOf(slo.timeWindow.duration.unit) + ); + } else { + return timestampMoment.isAfter( + moment(Date.now()).subtract(slo.timeWindow.duration.asSeconds(), 's') + ); + } + }); + break; + default: + assertNever(purgeType); + } + + if (inputIsInvalid) { + throw new IllegalArgumentError( + `The provided purge policy is invalid. At least one SLO has a time window that is longer than the provided purge policy.` + ); + } + } + + private getTimestamp(purgePolicy: BulkPurgeRollupParams['purgePolicy']): string { + if (purgePolicy.purgeType === 'fixed_age') { + return `now-${purgePolicy.age.format()}`; + } else if (purgePolicy.purgeType === 'fixed_time') { + return purgePolicy.timestamp.toISOString(); + } + assertNever(purgePolicy); + } +} diff --git a/x-pack/solutions/observability/plugins/slo/server/services/index.ts b/x-pack/solutions/observability/plugins/slo/server/services/index.ts index b56a74baf7c38..34aea8174ffd6 100644 --- a/x-pack/solutions/observability/plugins/slo/server/services/index.ts +++ b/x-pack/solutions/observability/plugins/slo/server/services/index.ts @@ -9,6 +9,7 @@ export * from './create_slo'; export * from './delete_slo'; export * from './delete_slo_instances'; export * from './find_slo'; +export * from './bulk_purge_rollup_data'; export * from './get_slo'; export * from './historical_summary_client'; export * from './resource_installer'; diff --git a/x-pack/solutions/observability/plugins/slo/server/services/purge_rollup_data.test.ts b/x-pack/solutions/observability/plugins/slo/server/services/purge_rollup_data.test.ts new file mode 100644 index 0000000000000..34a81a374a1b0 --- /dev/null +++ b/x-pack/solutions/observability/plugins/slo/server/services/purge_rollup_data.test.ts @@ -0,0 +1,164 @@ +/* + * 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 { ElasticsearchClient } from '@kbn/core/server'; +import { elasticsearchServiceMock } from '@kbn/core/server/mocks'; +import { Duration, DurationUnit } from '@kbn/slo-schema'; +import { createSLO, createSLOWithCalendarTimeWindow } from './fixtures/slo'; +import { createSLORepositoryMock } from './mocks'; +import { SLORepository } from './slo_repository'; +import { BulkPurgeRollupData } from './bulk_purge_rollup_data'; +import { monthlyCalendarAligned } from './fixtures/time_window'; + +describe('purge rollup data', () => { + let mockRepository: jest.Mocked; + let mockEsClient: jest.Mocked; + let purgeRollupData: BulkPurgeRollupData; + + beforeEach(() => { + mockRepository = createSLORepositoryMock(); + mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + purgeRollupData = new BulkPurgeRollupData(mockEsClient, mockRepository); + }); + + describe('happy path', () => { + it('successfully makes a query to remove SLI data older than 30 days', async () => { + const slo = createSLO({ + id: 'test1', + // default includes 7 day rolling window + }); + mockRepository.findAllByIds.mockResolvedValueOnce([slo]); + + await purgeRollupData.execute({ + list: ['test1'], + purgePolicy: { purgeType: 'fixed_age', age: new Duration(30, DurationUnit.Day) }, + }); + + expect(mockRepository.findAllByIds).toMatchSnapshot(); + expect(mockEsClient.deleteByQuery).toMatchSnapshot(); + }); + + it('successfully makes a query to remove SLI data older than a week', async () => { + const slo = createSLOWithCalendarTimeWindow({ + id: 'test2', + }); + mockRepository.findAllByIds.mockResolvedValueOnce([slo]); + + await purgeRollupData.execute({ + list: ['test2'], + purgePolicy: { purgeType: 'fixed_age', age: new Duration(2, DurationUnit.Week) }, + }); + + expect(mockRepository.findAllByIds).toMatchSnapshot(); + expect(mockEsClient.deleteByQuery).toMatchSnapshot(); + }); + + it('successfully makes a query to remove SLI data based on a timestamp - month', async () => { + const slo = createSLO({ + id: 'test3', + timeWindow: monthlyCalendarAligned(), + }); + mockRepository.findAllByIds.mockResolvedValueOnce([slo]); + + await purgeRollupData.execute({ + list: ['test3'], + purgePolicy: { purgeType: 'fixed_time', timestamp: new Date('2025-03-01T00:00:00Z') }, + }); + + expect(mockEsClient.deleteByQuery).toMatchSnapshot(); + }); + + it('successfully makes a query to remove SLI data based on a timestamp - week', async () => { + const slo = createSLOWithCalendarTimeWindow({ + id: 'test4', + }); + mockRepository.findAllByIds.mockResolvedValueOnce([slo]); + + await purgeRollupData.execute({ + list: ['test4'], + purgePolicy: { purgeType: 'fixed_time', timestamp: new Date('2025-04-01T00:00:00Z') }, + }); + + expect(mockEsClient.deleteByQuery).toMatchSnapshot(); + }); + + it('successfully makes a forced query to remove recently added SLI data', async () => { + const slo = createSLOWithCalendarTimeWindow({ + id: 'test5', + }); + mockRepository.findAllByIds.mockResolvedValueOnce([slo]); + + await purgeRollupData.execute({ + list: ['test5'], + purgePolicy: { + purgeType: 'fixed_age', + age: new Duration(1, DurationUnit.Day), + }, + force: true, + }); + + expect(mockEsClient.deleteByQuery).toMatchSnapshot(); + }); + }); + + describe('error path', () => { + it('fails to make a query to remove SLI data older than 7 days', async () => { + const slo = createSLO({ + id: 'test1', + // default includes 7 day rolling window + }); + mockRepository.findAllByIds.mockResolvedValueOnce([slo]); + + await expect( + purgeRollupData.execute({ + list: ['test1'], + purgePolicy: { purgeType: 'fixed_age', age: new Duration(3, DurationUnit.Day) }, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"The provided purge policy is invalid. At least one SLO has a time window that is longer than the provided purge policy."` + ); + + expect(mockEsClient.deleteByQuery).toHaveBeenCalledTimes(0); + }); + + it('fails to make a query to remove SLI data older than a day', async () => { + const slo = createSLOWithCalendarTimeWindow({ + id: 'test2', + }); + mockRepository.findAllByIds.mockResolvedValueOnce([slo]); + + await expect( + purgeRollupData.execute({ + list: ['test2'], + purgePolicy: { purgeType: 'fixed_age', age: new Duration(1, DurationUnit.Day) }, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"The provided purge policy is invalid. At least one SLO has a time window that is longer than the provided purge policy."` + ); + + expect(mockEsClient.deleteByQuery).toHaveBeenCalledTimes(0); + }); + + it('fails to makes a query to remove SLI data based on a timestamp', async () => { + const slo = createSLOWithCalendarTimeWindow({ + id: 'test3', + }); + mockRepository.findAllByIds.mockResolvedValueOnce([slo]); + + await expect( + purgeRollupData.execute({ + list: ['test3'], + purgePolicy: { purgeType: 'fixed_time', timestamp: new Date() }, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"The provided purge policy is invalid. At least one SLO has a time window that is longer than the provided purge policy."` + ); + + expect(mockEsClient.deleteByQuery).toHaveBeenCalledTimes(0); + }); + }); +}); diff --git a/x-pack/test/api_integration/deployment_agnostic/services/slo_api.ts b/x-pack/test/api_integration/deployment_agnostic/services/slo_api.ts index e5c812baa9b71..b74f3f3493903 100644 --- a/x-pack/test/api_integration/deployment_agnostic/services/slo_api.ts +++ b/x-pack/test/api_integration/deployment_agnostic/services/slo_api.ts @@ -158,5 +158,25 @@ export function SloApiProvider({ getService }: DeploymentAgnosticFtrProviderCont }) ); }, + + async purgeRollupData( + ids: string[], + purgePolicy: { purgeType: 'fixed_age' | 'fixed_time'; age?: string; timestamp?: Date }, + roleAuthc: RoleCredentials, + expectedStatus: number, + force: boolean = false + ) { + const { body } = await supertestWithoutAuth + .post(`/api/observability/slos/_bulk_purge_rollup${force ? '?force=true' : ''}`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .send({ + ids, + purgePolicy, + }) + .expect(expectedStatus); + + return body; + }, }; }