diff --git a/packages/kbn-slo-schema/src/rest_specs/slo.ts b/packages/kbn-slo-schema/src/rest_specs/slo.ts index 8fd6e757c6ffb6..86b56520bf21f6 100644 --- a/packages/kbn-slo-schema/src/rest_specs/slo.ts +++ b/packages/kbn-slo-schema/src/rest_specs/slo.ts @@ -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; type SLOWithSummaryResponse = t.OutputOf; @@ -147,6 +151,7 @@ export { deleteSLOParamsSchema, findSLOParamsSchema, findSLOResponseSchema, + getSLODiagnosisParamsSchema, getSLOParamsSchema, getSLOResponseSchema, fetchHistoricalSummaryParamsSchema, diff --git a/x-pack/plugins/observability/server/routes/slo/route.ts b/x-pack/plugins/observability/server/routes/slo/route.ts index 8e876b81015229..1aec94e3dde1f0 100644 --- a/x-pack/plugins/observability/server/routes/slo/route.ts +++ b/x-pack/plugins/observability/server/routes/slo/route.ts @@ -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 = { '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, }; diff --git a/x-pack/plugins/observability/server/services/slo/get_diagnosis.ts b/x-pack/plugins/observability/server/services/slo/get_diagnosis.ts new file mode 100644 index 00000000000000..1879cb5521abb5 --- /dev/null +++ b/x-pack/plugins/observability/server/services/slo/get_diagnosis.ts @@ -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(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, + }; +} diff --git a/x-pack/plugins/observability/tsconfig.json b/x-pack/plugins/observability/tsconfig.json index 37471473183d77..5839d4d715d178 100644 --- a/x-pack/plugins/observability/tsconfig.json +++ b/x-pack/plugins/observability/tsconfig.json @@ -73,6 +73,7 @@ "@kbn/alerts-as-data-utils", "@kbn/core-application-browser", "@kbn/files-plugin", + "@kbn/core-elasticsearch-server", ], "exclude": [ "target/**/*",