mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
feat(slo): delete slo instances (#165270)
This commit is contained in:
parent
772bc0c598
commit
889f067187
11 changed files with 506 additions and 6 deletions
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -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 } },
|
||||
],
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue