feat(slo): delete slo instances (#165270)

This commit is contained in:
Kevin Delemme 2023-09-05 16:02:05 -04:00 committed by GitHub
parent 772bc0c598
commit 889f067187
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 506 additions and 6 deletions

View file

@ -158,6 +158,10 @@ const findSLOResponseSchema = t.type({
results: t.array(sloWithSummaryResponseSchema),
});
const deleteSLOInstancesParamsSchema = t.type({
body: t.type({ list: t.array(t.type({ sloId: sloIdSchema, instanceId: t.string })) }),
});
const fetchHistoricalSummaryParamsSchema = t.type({
body: t.type({ list: t.array(t.type({ sloId: sloIdSchema, instanceId: allOrAnyString })) }),
});
@ -239,6 +243,9 @@ type UpdateSLOResponse = t.OutputOf<typeof updateSLOResponseSchema>;
type FindSLOParams = t.TypeOf<typeof findSLOParamsSchema.props.query>;
type FindSLOResponse = t.OutputOf<typeof findSLOResponseSchema>;
type DeleteSLOInstancesInput = t.OutputOf<typeof deleteSLOInstancesParamsSchema.props.body>;
type DeleteSLOInstancesParams = t.TypeOf<typeof deleteSLOInstancesParamsSchema.props.body>;
type FetchHistoricalSummaryParams = t.TypeOf<typeof fetchHistoricalSummaryParamsSchema.props.body>;
type FetchHistoricalSummaryResponse = t.OutputOf<typeof fetchHistoricalSummaryResponseSchema>;
type HistoricalSummaryResponse = t.OutputOf<typeof historicalSummarySchema>;
@ -269,6 +276,7 @@ type KQLCustomIndicator = t.OutputOf<typeof kqlCustomIndicatorSchema>;
export {
createSLOParamsSchema,
deleteSLOParamsSchema,
deleteSLOInstancesParamsSchema,
findSLOParamsSchema,
findSLOResponseSchema,
getPreviewDataParamsSchema,
@ -294,6 +302,8 @@ export type {
CreateSLOInput,
CreateSLOParams,
CreateSLOResponse,
DeleteSLOInstancesInput,
DeleteSLOInstancesParams,
FindSLOParams,
FindSLOResponse,
GetPreviewDataParams,

View file

@ -677,6 +677,74 @@
}
}
}
},
"/s/{spaceId}/api/observability/slos/_delete_instances": {
"post": {
"summary": "Batch delete rollup and summary data for the matching list of sloId and instanceId",
"operationId": "deleteSloInstancesOp",
"description": "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/delete_slo_instances_request"
}
}
}
},
"responses": {
"204": {
"description": "Successful request"
},
"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"
}
}
}
}
},
"servers": [
{
"url": "https://localhost:5601"
}
]
}
}
},
"components": {
@ -1718,10 +1786,10 @@
"title": "Historical summary request",
"type": "object",
"required": [
"sloIds"
"list"
],
"properties": {
"sloIds": {
"list": {
"description": "The list of SLO identifiers to get the historical summary for",
"type": "array",
"items": {
@ -1756,6 +1824,39 @@
}
}
}
},
"delete_slo_instances_request": {
"title": "Delete SLO instances request",
"description": "The delete SLO instances request takes a list of SLO id and instance id, then delete the rollup and summary data. This API can be used to remove the staled data of an instance SLO that no longer get updated.\n",
"type": "object",
"required": [
"list"
],
"properties": {
"list": {
"description": "An array of slo id and instance id",
"type": "array",
"items": {
"type": "object",
"required": [
"sloId",
"instanceId"
],
"properties": {
"sloId": {
"description": "The SLO unique identifier",
"type": "string",
"example": "8853df00-ae2e-11ed-90af-09bb6422b258"
},
"instanceId": {
"description": "The SLO instance identifier",
"type": "string",
"example": "8853df00-ae2e-11ed-90af-09bb6422b258"
}
}
}
}
}
}
}
}

View file

@ -408,6 +408,46 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/403_response'
/s/{spaceId}/api/observability/slos/_delete_instances:
post:
summary: Batch delete rollup and summary data for the matching list of sloId and instanceId
operationId: deleteSloInstancesOp
description: |
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/delete_slo_instances_request'
responses:
'204':
description: Successful request
'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'
servers:
- url: https://localhost:5601
components:
securitySchemes:
basicAuth:
@ -1190,9 +1230,9 @@ components:
title: Historical summary request
type: object
required:
- sloIds
- list
properties:
sloIds:
list:
description: The list of SLO identifiers to get the historical summary for
type: array
items:
@ -1216,3 +1256,28 @@ components:
example: 0.9836
errorBudget:
$ref: '#/components/schemas/error_budget'
delete_slo_instances_request:
title: Delete SLO instances request
description: |
The delete SLO instances request takes a list of SLO id and instance id, then delete the rollup and summary data. This API can be used to remove the staled data of an instance SLO that no longer get updated.
type: object
required:
- list
properties:
list:
description: An array of slo id and instance id
type: array
items:
type: object
required:
- sloId
- instanceId
properties:
sloId:
description: The SLO unique identifier
type: string
example: 8853df00-ae2e-11ed-90af-09bb6422b258
instanceId:
description: The SLO instance identifier
type: string
example: 8853df00-ae2e-11ed-90af-09bb6422b258

View file

@ -0,0 +1,26 @@
title: Delete SLO instances request
description: >
The delete SLO instances request takes a list of SLO id and instance id, then delete the rollup and summary data.
This API can be used to remove the staled data of an instance SLO that no longer get updated.
type: object
required:
- list
properties:
list:
description: An array of slo id and instance id
type: array
items:
type: object
required:
- sloId
- instanceId
properties:
sloId:
description: The SLO unique identifier
type: string
example: 8853df00-ae2e-11ed-90af-09bb6422b258
instanceId:
description: The SLO instance identifier
type: string
example: 8853df00-ae2e-11ed-90af-09bb6422b258

View file

@ -1,9 +1,9 @@
title: Historical summary request
type: object
required:
- sloIds
- list
properties:
sloIds:
list:
description: The list of SLO identifiers to get the historical summary for
type: array
items:

View file

@ -31,6 +31,8 @@ paths:
$ref: "paths/s@{spaceid}@api@slos@{sloid}@{disable}.yaml"
"/s/{spaceId}/internal/observability/slos/_historical_summary":
$ref: "paths/s@{spaceid}@api@slos@_historical_summary.yaml"
"/s/{spaceId}/api/observability/slos/_delete_instances":
$ref: "paths/s@{spaceid}@api@slos@_delete_instances.yaml"
components:
securitySchemes:
basicAuth:

View file

@ -0,0 +1,40 @@
post:
summary: Batch delete rollup and summary data for the matching list of sloId and instanceId
operationId: deleteSloInstancesOp
description: >
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/delete_slo_instances_request.yaml'
responses:
'204':
description: Successful request
'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'
servers:
- url: https://localhost:5601

View file

@ -9,6 +9,7 @@ import { errors } from '@elastic/elasticsearch';
import { failedDependency, forbidden } from '@hapi/boom';
import {
createSLOParamsSchema,
deleteSLOInstancesParamsSchema,
deleteSLOParamsSchema,
fetchHistoricalSummaryParamsSchema,
findSloDefinitionsParamsSchema,
@ -26,6 +27,7 @@ import {
DefaultSummaryClient,
DefaultTransformManager,
DeleteSLO,
DeleteSLOInstances,
FindSLO,
GetSLO,
KibanaSavedObjectsSLORepository,
@ -225,6 +227,22 @@ const findSLORoute = createObservabilityServerRoute({
},
});
const deleteSloInstancesRoute = createObservabilityServerRoute({
endpoint: 'POST /api/observability/slos/_delete_instances 2023-10-31',
options: {
tags: ['access:slo_write'],
},
params: deleteSLOInstancesParamsSchema,
handler: async ({ context, params }) => {
await assertPlatinumLicense(context);
const esClient = (await context.core).elasticsearch.client.asCurrentUser;
const deleteSloInstances = new DeleteSLOInstances(esClient);
await deleteSloInstances.execute(params.body);
},
});
const findSloDefinitionsRoute = createObservabilityServerRoute({
endpoint: 'GET /internal/observability/slos/_definitions',
options: {
@ -351,6 +369,7 @@ const getPreviewData = createObservabilityServerRoute({
export const sloRouteRepository = {
...createSLORoute,
...deleteSLORoute,
...deleteSloInstancesRoute,
...disableSLORoute,
...enableSLORoute,
...fetchHistoricalSummary,

View file

@ -0,0 +1,165 @@
/*
* 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 { DeleteSLOInstances } from './delete_slo_instances';
describe('DeleteSLOInstances', () => {
let mockEsClient: jest.Mocked<ElasticsearchClient>;
let deleteSLOInstances: DeleteSLOInstances;
beforeEach(() => {
mockEsClient = elasticsearchServiceMock.createElasticsearchClient();
deleteSLOInstances = new DeleteSLOInstances(mockEsClient);
});
describe('validation', () => {
it("forbids deleting an SLO with an '*' (all) instance id", async () => {
await expect(
deleteSLOInstances.execute({
list: [
{ sloId: 'first', instanceId: 'irrelevant' },
{ sloId: 'second', instanceId: '*' },
],
})
).rejects.toThrowError("Cannot delete an SLO instance '*'");
});
});
it('deletes the roll up and the summary data for each tuple', async () => {
await deleteSLOInstances.execute({
list: [
{ sloId: 'first', instanceId: 'host-foo' },
{ sloId: 'second', instanceId: 'host-foo' },
{ sloId: 'third', instanceId: 'cluster-eu' },
],
});
expect(mockEsClient.deleteByQuery).toHaveBeenCalledTimes(2);
expect(mockEsClient.deleteByQuery.mock.calls[0][0]).toMatchInlineSnapshot(`
Object {
"index": ".slo-observability.sli-v2*",
"query": Object {
"bool": Object {
"should": Array [
Object {
"bool": Object {
"must": Array [
Object {
"term": Object {
"slo.id": "first",
},
},
Object {
"term": Object {
"slo.instanceId": "host-foo",
},
},
],
},
},
Object {
"bool": Object {
"must": Array [
Object {
"term": Object {
"slo.id": "second",
},
},
Object {
"term": Object {
"slo.instanceId": "host-foo",
},
},
],
},
},
Object {
"bool": Object {
"must": Array [
Object {
"term": Object {
"slo.id": "third",
},
},
Object {
"term": Object {
"slo.instanceId": "cluster-eu",
},
},
],
},
},
],
},
},
"wait_for_completion": false,
}
`);
expect(mockEsClient.deleteByQuery.mock.calls[1][0]).toMatchInlineSnapshot(`
Object {
"index": ".slo-observability.summary-v2*",
"query": Object {
"bool": Object {
"should": Array [
Object {
"bool": Object {
"must": Array [
Object {
"term": Object {
"slo.id": "first",
},
},
Object {
"term": Object {
"slo.instanceId": "host-foo",
},
},
],
},
},
Object {
"bool": Object {
"must": Array [
Object {
"term": Object {
"slo.id": "second",
},
},
Object {
"term": Object {
"slo.instanceId": "host-foo",
},
},
],
},
},
Object {
"bool": Object {
"must": Array [
Object {
"term": Object {
"slo.id": "third",
},
},
Object {
"term": Object {
"slo.instanceId": "cluster-eu",
},
},
],
},
},
],
},
},
"wait_for_completion": false,
}
`);
});
});

View file

@ -0,0 +1,71 @@
/*
* 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 { ALL_VALUE, DeleteSLOInstancesParams } from '@kbn/slo-schema';
import {
SLO_DESTINATION_INDEX_PATTERN,
SLO_SUMMARY_DESTINATION_INDEX_PATTERN,
} from '../../assets/constants';
import { IllegalArgumentError } from '../../errors';
interface SloInstanceTuple {
sloId: string;
instanceId: string;
}
export class DeleteSLOInstances {
constructor(private esClient: ElasticsearchClient) {}
public async execute(params: DeleteSLOInstancesParams): Promise<void> {
const containsAllValueInstanceId = params.list.some((item) => item.instanceId === ALL_VALUE);
if (containsAllValueInstanceId) {
throw new IllegalArgumentError("Cannot delete an SLO instance '*'");
}
await this.deleteRollupData(params.list);
await this.deleteSummaryData(params.list);
}
private async deleteRollupData(list: SloInstanceTuple[]): Promise<void> {
await this.esClient.deleteByQuery({
index: SLO_DESTINATION_INDEX_PATTERN,
wait_for_completion: false,
query: {
bool: {
should: list.map((item) => ({
bool: {
must: [
{ term: { 'slo.id': item.sloId } },
{ term: { 'slo.instanceId': item.instanceId } },
],
},
})),
},
},
});
}
private async deleteSummaryData(list: SloInstanceTuple[]): Promise<void> {
await this.esClient.deleteByQuery({
index: SLO_SUMMARY_DESTINATION_INDEX_PATTERN,
wait_for_completion: false,
query: {
bool: {
should: list.map((item) => ({
bool: {
must: [
{ term: { 'slo.id': item.sloId } },
{ term: { 'slo.instanceId': item.instanceId } },
],
},
})),
},
},
});
}
}

View file

@ -7,6 +7,7 @@
export * from './create_slo';
export * from './delete_slo';
export * from './delete_slo_instances';
export * from './fetch_historical_summary';
export * from './find_slo';
export * from './get_slo';