Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(slo): manage slo state #149546

Merged
merged 5 commits into from
Jan 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/kbn-slo-schema/src/rest_specs/slo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ const sloResponseSchema = t.type({
objective: objectiveSchema,
revision: t.number,
settings: settingsSchema,
enabled: t.boolean,
createdAt: dateType,
updatedAt: dateType,
});
Expand All @@ -101,6 +102,10 @@ const updateSLOParamsSchema = t.type({
}),
});

const manageSLOParamsSchema = t.type({
path: t.type({ id: t.string }),
});

const updateSLOResponseSchema = sloResponseSchema;

const findSLOResponseSchema = t.type({
Expand All @@ -122,6 +127,8 @@ type CreateSLOResponse = t.TypeOf<typeof createSLOResponseSchema>; // Raw respon

type GetSLOResponse = t.OutputOf<typeof getSLOResponseSchema>;

type ManageSLOParams = t.TypeOf<typeof manageSLOParamsSchema.props.path>;

type UpdateSLOInput = t.OutputOf<typeof updateSLOParamsSchema.props.body>;
type UpdateSLOParams = t.TypeOf<typeof updateSLOParamsSchema.props.body>;
type UpdateSLOResponse = t.OutputOf<typeof updateSLOResponseSchema>;
Expand All @@ -144,6 +151,7 @@ export {
getSLOResponseSchema,
fetchHistoricalSummaryParamsSchema,
fetchHistoricalSummaryResponseSchema,
manageSLOParamsSchema,
sloResponseSchema,
sloWithSummaryResponseSchema,
updateSLOParamsSchema,
Expand All @@ -160,6 +168,7 @@ export type {
FetchHistoricalSummaryParams,
FetchHistoricalSummaryResponse,
HistoricalSummaryResponse,
ManageSLOParams,
SLOResponse,
SLOWithSummaryResponse,
UpdateSLOInput,
Expand Down
1 change: 1 addition & 0 deletions packages/kbn-slo-schema/src/schema/slo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const sloSchema = t.type({
objective: objectiveSchema,
settings: settingsSchema,
revision: t.number,
enabled: t.boolean,
createdAt: dateType,
updatedAt: dateType,
});
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/observability/public/data/slo/slo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const baseSlo: Omit<SLOWithSummaryResponse, 'id'> = {
isEstimated: false,
},
},
enabled: true,
createdAt: now,
updatedAt: now,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export function transformSloResponseToCreateSloInput(
if (!values) return undefined;

return {
...omit(values, ['id', 'revision', 'createdAt', 'updatedAt', 'summary']),
...omit(values, ['id', 'revision', 'createdAt', 'updatedAt', 'summary', 'enabled']),
objective: {
target: values.objective.target * 100,
...(values.objective.timesliceTarget && {
Expand Down
54 changes: 53 additions & 1 deletion x-pack/plugins/observability/server/routes/slo/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
fetchHistoricalSummaryParamsSchema,
findSLOParamsSchema,
getSLOParamsSchema,
manageSLOParamsSchema,
updateSLOParamsSchema,
} from '@kbn/slo-schema';
import {
Expand All @@ -36,6 +37,7 @@ import { DefaultHistoricalSummaryClient } from '../../services/slo/historical_su
import { FetchHistoricalSummary } from '../../services/slo/fetch_historical_summary';
import type { IndicatorTypes } from '../../domain/models';
import type { ObservabilityRequestHandlerContext } from '../../types';
import { ManageSLO } from '../../services/slo/manage_slo';

const transformGenerators: Record<IndicatorTypes, TransformGenerator> = {
'sli.apm.transactionDuration': new ApmTransactionDurationTransformGenerator(),
Expand Down Expand Up @@ -142,6 +144,54 @@ const getSLORoute = createObservabilityServerRoute({
},
});

const enableSLORoute = createObservabilityServerRoute({
endpoint: 'POST /api/observability/slos/{id}/enable',
options: {
tags: [],
},
params: manageSLOParamsSchema,
handler: async ({ context, params, logger }) => {
if (!isLicenseAtLeastPlatinum(context)) {
throw badRequest('Platinum license or higher is needed to make use of this feature.');
}

const soClient = (await context.core).savedObjects.client;
const esClient = (await context.core).elasticsearch.client.asCurrentUser;

const repository = new KibanaSavedObjectsSLORepository(soClient);
const transformManager = new DefaultTransformManager(transformGenerators, esClient, logger);
const manageSLO = new ManageSLO(repository, transformManager);

const response = await manageSLO.enable(params.path.id);

return response;
},
});

const disableSLORoute = createObservabilityServerRoute({
endpoint: 'POST /api/observability/slos/{id}/disable',
options: {
tags: [],
},
params: manageSLOParamsSchema,
handler: async ({ context, params, logger }) => {
if (!isLicenseAtLeastPlatinum(context)) {
throw badRequest('Platinum license or higher is needed to make use of this feature.');
}

const soClient = (await context.core).savedObjects.client;
const esClient = (await context.core).elasticsearch.client.asCurrentUser;

const repository = new KibanaSavedObjectsSLORepository(soClient);
const transformManager = new DefaultTransformManager(transformGenerators, esClient, logger);
const manageSLO = new ManageSLO(repository, transformManager);

const response = await manageSLO.disable(params.path.id);

return response;
},
});

const findSLORoute = createObservabilityServerRoute({
endpoint: 'GET /api/observability/slos',
options: {
Expand Down Expand Up @@ -191,8 +241,10 @@ const fetchHistoricalSummary = createObservabilityServerRoute({
export const slosRouteRepository = {
...createSLORoute,
...deleteSLORoute,
...disableSLORoute,
...enableSLORoute,
...fetchHistoricalSummary,
...findSLORoute,
...getSLORoute,
...fetchHistoricalSummary,
...updateSLORoute,
};
1 change: 1 addition & 0 deletions x-pack/plugins/observability/server/saved_objects/slo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export const slo: SavedObjectsType = {
},
},
revision: { type: 'short' },
enabled: { type: 'boolean' },
createdAt: { type: 'date' },
updatedAt: { type: 'date' },
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export class CreateSLO {
frequency: params.settings?.frequency ?? new Duration(1, DurationUnit.Minute),
},
revision: 1,
enabled: true,
createdAt: now,
updatedAt: now,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ describe('FindSLO', () => {
},
createdAt: slo.createdAt.toISOString(),
updatedAt: slo.updatedAt.toISOString(),
enabled: slo.enabled,
revision: slo.revision,
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const defaultSLO: Omit<SLO, 'id' | 'revision' | 'createdAt' | 'updatedAt'> = {
syncDelay: new Duration(1, DurationUnit.Minute),
frequency: new Duration(1, DurationUnit.Minute),
},
enabled: true,
};

export const createSLOParams = (params: Partial<CreateSLOParams> = {}): CreateSLOParams => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ describe('GetSLO', () => {
},
createdAt: slo.createdAt.toISOString(),
updatedAt: slo.updatedAt.toISOString(),
enabled: slo.enabled,
revision: slo.revision,
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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 { createSLO } from './fixtures/slo';
import { ManageSLO } from './manage_slo';
import { createSLORepositoryMock, createTransformManagerMock } from './mocks';
import { SLORepository } from './slo_repository';
import { TransformManager } from './transform_manager';

describe('ManageSLO', () => {
let mockRepository: jest.Mocked<SLORepository>;
let mockTransformManager: jest.Mocked<TransformManager>;
let manageSLO: ManageSLO;

beforeEach(() => {
mockRepository = createSLORepositoryMock();
mockTransformManager = createTransformManagerMock();
manageSLO = new ManageSLO(mockRepository, mockTransformManager);
});

describe('Enable', () => {
it('does nothing when slo is already enabled', async () => {
const slo = createSLO({ enabled: true });
mockRepository.findById.mockResolvedValue(slo);

await manageSLO.enable(slo.id);

expect(mockTransformManager.start).not.toHaveBeenCalled();
expect(mockRepository.save).not.toHaveBeenCalled();
});

it('enables the slo when disabled', async () => {
const slo = createSLO({ enabled: false });
mockRepository.findById.mockResolvedValue(slo);

await manageSLO.enable(slo.id);

expect(mockTransformManager.start).toHaveBeenCalled();
expect(mockRepository.save).toHaveBeenCalledWith(expect.objectContaining({ enabled: true }));
});
});

describe('Disable', () => {
it('does nothing when slo is already disabled', async () => {
const slo = createSLO({ enabled: false });
mockRepository.findById.mockResolvedValue(slo);

await manageSLO.disable(slo.id);

expect(mockTransformManager.stop).not.toHaveBeenCalled();
expect(mockRepository.save).not.toHaveBeenCalled();
});

it('disables the slo when enabled', async () => {
const slo = createSLO({ enabled: true });
mockRepository.findById.mockResolvedValue(slo);

await manageSLO.disable(slo.id);

expect(mockTransformManager.stop).toHaveBeenCalled();
expect(mockRepository.save).toHaveBeenCalledWith(expect.objectContaining({ enabled: false }));
});
});
});
38 changes: 38 additions & 0 deletions x-pack/plugins/observability/server/services/slo/manage_slo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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 { getSLOTransformId } from '../../assets/constants';
import { SLORepository } from './slo_repository';
import { TransformManager } from './transform_manager';

export class ManageSLO {
constructor(private repository: SLORepository, private transformManager: TransformManager) {}

async enable(sloId: string) {
const slo = await this.repository.findById(sloId);
if (slo.enabled) {
return;
}

await this.transformManager.start(getSLOTransformId(slo.id, slo.revision));
slo.enabled = true;
slo.updatedAt = new Date();
await this.repository.save(slo);
}

async disable(sloId: string) {
const slo = await this.repository.findById(sloId);
if (!slo.enabled) {
return;
}

await this.transformManager.stop(getSLOTransformId(slo.id, slo.revision));
slo.enabled = false;
slo.updatedAt = new Date();
await this.repository.save(slo);
}
}
14 changes: 1 addition & 13 deletions x-pack/plugins/observability/server/services/slo/update_slo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,18 +93,6 @@ export class UpdateSLO {
}

private toResponse(slo: SLO): UpdateSLOResponse {
return updateSLOResponseSchema.encode({
id: slo.id,
name: slo.name,
description: slo.description,
indicator: slo.indicator,
budgetingMethod: slo.budgetingMethod,
timeWindow: slo.timeWindow,
objective: slo.objective,
settings: slo.settings,
revision: slo.revision,
createdAt: slo.createdAt,
updatedAt: slo.updatedAt,
});
return updateSLOResponseSchema.encode(slo);
}
}