feat(slo): global and slo specific diagnosis (#153118)

This commit is contained in:
Kevin Delemme 2023-03-14 15:20:09 -04:00 committed by GitHub
parent 0343c634b2
commit c226c07c03
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 141 additions and 0 deletions

View file

@ -118,6 +118,10 @@ const findSLOResponseSchema = t.type({
const fetchHistoricalSummaryParamsSchema = t.type({ body: t.type({ sloIds: t.array(t.string) }) });
const fetchHistoricalSummaryResponseSchema = t.record(t.string, t.array(historicalSummarySchema));
const getSLODiagnosisParamsSchema = t.type({
path: t.type({ id: t.string }),
});
type SLOResponse = t.OutputOf<typeof sloResponseSchema>;
type SLOWithSummaryResponse = t.OutputOf<typeof sloWithSummaryResponseSchema>;
@ -147,6 +151,7 @@ export {
deleteSLOParamsSchema,
findSLOParamsSchema,
findSLOResponseSchema,
getSLODiagnosisParamsSchema,
getSLOParamsSchema,
getSLOResponseSchema,
fetchHistoricalSummaryParamsSchema,

View file

@ -11,6 +11,7 @@ import {
deleteSLOParamsSchema,
fetchHistoricalSummaryParamsSchema,
findSLOParamsSchema,
getSLODiagnosisParamsSchema,
getSLOParamsSchema,
manageSLOParamsSchema,
updateSLOParamsSchema,
@ -38,6 +39,7 @@ import { FetchHistoricalSummary } from '../../services/slo/fetch_historical_summ
import type { IndicatorTypes } from '../../domain/models';
import type { ObservabilityRequestHandlerContext } from '../../types';
import { ManageSLO } from '../../services/slo/manage_slo';
import { getGlobalDiagnosis, getSloDiagnosis } from '../../services/slo/get_diagnosis';
const transformGenerators: Record<IndicatorTypes, TransformGenerator> = {
'sli.apm.transactionDuration': new ApmTransactionDurationTransformGenerator(),
@ -238,6 +240,34 @@ const fetchHistoricalSummary = createObservabilityServerRoute({
},
});
const getDiagnosisRoute = createObservabilityServerRoute({
endpoint: 'GET /internal/observability/slos/_diagnosis',
options: {
tags: [],
},
params: undefined,
handler: async ({ context }) => {
const esClient = (await context.core).elasticsearch.client.asCurrentUser;
const licensing = await context.licensing;
return getGlobalDiagnosis(esClient, licensing);
},
});
const getSloDiagnosisRoute = createObservabilityServerRoute({
endpoint: 'GET /internal/observability/slos/{id}/_diagnosis',
options: {
tags: [],
},
params: getSLODiagnosisParamsSchema,
handler: async ({ context, params }) => {
const esClient = (await context.core).elasticsearch.client.asCurrentUser;
const soClient = (await context.core).savedObjects.client;
return getSloDiagnosis(params.path.id, { esClient, soClient });
},
});
export const slosRouteRepository = {
...createSLORoute,
...deleteSLORoute,
@ -247,4 +277,6 @@ export const slosRouteRepository = {
...findSLORoute,
...getSLORoute,
...updateSLORoute,
...getDiagnosisRoute,
...getSloDiagnosisRoute,
};

View file

@ -0,0 +1,103 @@
/*
* 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-elasticsearch-server';
import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
import { LicensingApiRequestHandlerContext } from '@kbn/licensing-plugin/server';
import {
getSLOTransformId,
SLO_COMPONENT_TEMPLATE_MAPPINGS_NAME,
SLO_COMPONENT_TEMPLATE_SETTINGS_NAME,
SLO_INDEX_TEMPLATE_NAME,
SLO_INGEST_PIPELINE_NAME,
} from '../../assets/constants';
import { StoredSLO } from '../../domain/models';
import { SO_SLO_TYPE } from '../../saved_objects';
const OK = 'OK';
const NOT_OK = 'NOT_OK';
export async function getGlobalDiagnosis(
esClient: ElasticsearchClient,
licensing: LicensingApiRequestHandlerContext
) {
const licenseInfo = licensing.license.toJSON();
const userPrivileges = await esClient.security.getUserPrivileges();
const sloResources = await getSloResourcesDiagnosis(esClient);
return {
licenseAndFeatures: licenseInfo,
userPrivileges,
sloResources,
};
}
export async function getSloDiagnosis(
sloId: string,
services: { esClient: ElasticsearchClient; soClient: SavedObjectsClientContract }
) {
const { esClient, soClient } = services;
const sloResources = await getSloResourcesDiagnosis(esClient);
let sloSavedObject;
try {
sloSavedObject = await soClient.get<StoredSLO>(SO_SLO_TYPE, sloId);
} catch (err) {
// noop
}
const sloTransformStats = await esClient.transform.getTransformStats({
transform_id: getSLOTransformId(sloId, sloSavedObject?.attributes.revision ?? 1),
});
let dataSample;
if (sloSavedObject?.attributes.indicator.params.index) {
const slo = sloSavedObject.attributes;
dataSample = await esClient.search({
index: slo.indicator.params.index,
sort: { [slo.settings.timestampField]: 'desc' },
size: 5,
});
}
return {
sloResources,
sloSavedObject: sloSavedObject ?? NOT_OK,
sloTransformStats,
dataSample: dataSample ?? NOT_OK,
};
}
async function getSloResourcesDiagnosis(esClient: ElasticsearchClient) {
const indexTemplateExists = await esClient.indices.existsIndexTemplate({
name: SLO_INDEX_TEMPLATE_NAME,
});
const mappingsTemplateExists = await esClient.cluster.existsComponentTemplate({
name: SLO_COMPONENT_TEMPLATE_MAPPINGS_NAME,
});
const settingsTemplateExists = await esClient.cluster.existsComponentTemplate({
name: SLO_COMPONENT_TEMPLATE_SETTINGS_NAME,
});
let ingestPipelineExists = true;
try {
await esClient.ingest.getPipeline({ id: SLO_INGEST_PIPELINE_NAME });
} catch (err) {
ingestPipelineExists = false;
}
return {
[SLO_INDEX_TEMPLATE_NAME]: indexTemplateExists ? OK : NOT_OK,
[SLO_COMPONENT_TEMPLATE_MAPPINGS_NAME]: mappingsTemplateExists ? OK : NOT_OK,
[SLO_COMPONENT_TEMPLATE_SETTINGS_NAME]: settingsTemplateExists ? OK : NOT_OK,
[SLO_INGEST_PIPELINE_NAME]: ingestPipelineExists ? OK : NOT_OK,
};
}

View file

@ -73,6 +73,7 @@
"@kbn/alerts-as-data-utils",
"@kbn/core-application-browser",
"@kbn/files-plugin",
"@kbn/core-elasticsearch-server",
],
"exclude": [
"target/**/*",