diff --git a/x-pack/platform/plugins/private/telemetry_collection_xpack/schema/xpack_observability.json b/x-pack/platform/plugins/private/telemetry_collection_xpack/schema/xpack_observability.json index 1905f858e1e3c..051e585f040c5 100644 --- a/x-pack/platform/plugins/private/telemetry_collection_xpack/schema/xpack_observability.json +++ b/x-pack/platform/plugins/private/telemetry_collection_xpack/schema/xpack_observability.json @@ -7251,7 +7251,39 @@ "total": { "type": "long", "_meta": { - "description": "The total number of slos in the cluster" + "description": "The total number of SLOs in the cluster" + } + }, + "definitions": { + "properties": { + "total": { + "type": "long", + "_meta": { + "description": "The total number of SLO definitions in the cluster" + } + }, + "total_with_ccs": { + "type": "long", + "_meta": { + "description": "The total number of SLO definitions using CCS in the cluster" + } + }, + "total_with_groups": { + "type": "long", + "_meta": { + "description": "The total number of SLO definitions using groups in the cluster" + } + } + } + }, + "instances": { + "properties": { + "total": { + "type": "long", + "_meta": { + "description": "The total number of SLO instances in the cluster" + } + } } }, "by_status": { @@ -7259,13 +7291,13 @@ "enabled": { "type": "long", "_meta": { - "description": "The number of enabled slos in the cluster" + "description": "The number of enabled SLOs in the cluster" } }, "disabled": { "type": "long", "_meta": { - "description": "The number of disabled slos in the cluster" + "description": "The number of disabled SLOs in the cluster" } } } @@ -7275,7 +7307,7 @@ "DYNAMIC_KEY": { "type": "long", "_meta": { - "description": "The number of slos by sli type in the cluster" + "description": "The number of SLOs by sli type in the cluster" } } } @@ -7285,7 +7317,7 @@ "DYNAMIC_KEY": { "type": "long", "_meta": { - "description": "The number of slos by rolling duration in the cluster" + "description": "The number of SLOs by rolling duration in the cluster" } } } @@ -7295,7 +7327,7 @@ "DYNAMIC_KEY": { "type": "long", "_meta": { - "description": "The number of slos by calendar aligned duration in the cluster" + "description": "The number of SLOs by calendar aligned duration in the cluster" } } } @@ -7305,13 +7337,13 @@ "occurrences": { "type": "long", "_meta": { - "description": "The number of slos by timeslices budgeting method in the cluster" + "description": "The number of SLOs by timeslices budgeting method in the cluster" } }, "timeslices": { "type": "long", "_meta": { - "description": "The number of slos by occurrences budgeting method in the cluster" + "description": "The number of SLOs by occurrences budgeting method in the cluster" } } } diff --git a/x-pack/solutions/observability/plugins/slo/jest.integration.config.js b/x-pack/solutions/observability/plugins/slo/jest.integration.config.js new file mode 100644 index 0000000000000..35005b040b82c --- /dev/null +++ b/x-pack/solutions/observability/plugins/slo/jest.integration.config.js @@ -0,0 +1,12 @@ +/* + * 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. + */ + +module.exports = { + preset: '@kbn/test/jest_integration', + rootDir: '../../../../..', + roots: ['/x-pack/solutions/observability/plugins/slo'], +}; diff --git a/x-pack/solutions/observability/plugins/slo/server/lib/collectors/fetcher.test.ts b/x-pack/solutions/observability/plugins/slo/server/lib/collectors/fetcher.test.ts deleted file mode 100644 index b099f986dbe05..0000000000000 --- a/x-pack/solutions/observability/plugins/slo/server/lib/collectors/fetcher.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 { savedObjectsRepositoryMock, ElasticsearchClientMock } from '@kbn/core/server/mocks'; -import { CollectorFetchContext } from '@kbn/usage-collection-plugin/server'; -import { fetcher } from './fetcher'; - -let savedObjectClient: ReturnType; - -let closeMock: jest.Mock; -let esClient: ElasticsearchClientMock; - -describe('SLO usage collector fetcher', () => { - beforeEach(() => { - savedObjectClient = savedObjectsRepositoryMock.create(); - closeMock = jest.fn(); - }); - - it('without any existing slo', async () => { - savedObjectClient.createPointInTimeFinder.mockReturnValue({ - find: async function* find() { - return { - [Symbol.asyncIterator]: async () => {}, - next: () => {}, - }; - }, - close: closeMock, - }); - - const results = await fetcher({ - soClient: savedObjectClient, - esClient, - } as CollectorFetchContext); - - expect(closeMock).toHaveBeenCalled(); - expect(results.slo.total).toEqual(0); - }); -}); diff --git a/x-pack/solutions/observability/plugins/slo/server/lib/collectors/fetcher.ts b/x-pack/solutions/observability/plugins/slo/server/lib/collectors/fetcher.ts index d5494452e6fcf..dfd47986ba38b 100644 --- a/x-pack/solutions/observability/plugins/slo/server/lib/collectors/fetcher.ts +++ b/x-pack/solutions/observability/plugins/slo/server/lib/collectors/fetcher.ts @@ -4,10 +4,13 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { isCCSRemoteIndexName } from '@kbn/es-query'; +import { ALL_VALUE } from '@kbn/slo-schema'; import { CollectorFetchContext } from '@kbn/usage-collection-plugin/server'; import { StoredSLODefinition } from '../../domain/models'; import { SO_SLO_TYPE } from '../../saved_objects'; import { Usage } from './type'; +import { SLO_SUMMARY_DESTINATION_INDEX_PATTERN } from '../../../common/constants'; export const fetcher = async (context: CollectorFetchContext) => { const finder = context.soClient.createPointInTimeFinder({ @@ -15,8 +18,31 @@ export const fetcher = async (context: CollectorFetchContext) => { perPage: 100, }); + const totalInstances = await context.esClient.count({ + index: SLO_SUMMARY_DESTINATION_INDEX_PATTERN, + query: { + bool: { + filter: [ + { + term: { + isTempDoc: false, + }, + }, + ], + }, + }, + }); + let usage: Usage['slo'] = { total: 0, + definitions: { + total: 0, + total_with_ccs: 0, + total_with_groups: 0, + }, + instances: { + total: totalInstances?.count ?? 0, + }, by_status: { enabled: 0, disabled: 0, @@ -34,7 +60,16 @@ export const fetcher = async (context: CollectorFetchContext) => { usage = response.saved_objects.reduce((acc, so) => { return { ...acc, - total: acc.total + 1, + total: acc.total + 1, // deprecated in favor of definitions.total + definitions: { + total: acc.definitions.total + 1, + total_with_ccs: isCCSRemoteIndexName(so.attributes.indicator.params.index) + ? acc.definitions.total_with_ccs + 1 + : acc.definitions.total_with_ccs, + total_with_groups: [so.attributes.groupBy].flat().includes(ALL_VALUE) + ? acc.definitions.total_with_groups + : acc.definitions.total_with_groups + 1, + }, by_status: { ...acc.by_status, ...(so.attributes.enabled && { enabled: acc.by_status.enabled + 1 }), diff --git a/x-pack/solutions/observability/plugins/slo/server/lib/collectors/integration_tests/fetcher.test.ts b/x-pack/solutions/observability/plugins/slo/server/lib/collectors/integration_tests/fetcher.test.ts new file mode 100644 index 0000000000000..cad41936ffe72 --- /dev/null +++ b/x-pack/solutions/observability/plugins/slo/server/lib/collectors/integration_tests/fetcher.test.ts @@ -0,0 +1,159 @@ +/* + * 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 { + createTestServers, + type TestElasticsearchUtils, + type TestKibanaUtils, +} from '@kbn/core-test-helpers-kbn-server'; +import { + SavedObjectsClient, + type ElasticsearchClient, + type Logger, + type SavedObjectsClientContract, +} from '@kbn/core/server'; +import { KibanaSavedObjectsSLORepository, SLORepository } from '../../../services'; +import { + createAPMTransactionDurationIndicator, + createAPMTransactionErrorRateIndicator, + createKQLCustomIndicator, + createMetricCustomIndicator, + createSLO, + createSLOWithTimeslicesBudgetingMethod, + createSyntheticsAvailabilityIndicator, + createTimesliceMetricIndicator, +} from '../../../services/fixtures/slo'; +import { fetcher } from '../fetcher'; + +const createLoggerMock = (): jest.Mocked => { + const logger = { + debug: jest.fn(), + info: jest.fn(), + error: jest.fn(), + get: jest.fn(), + } as unknown as jest.Mocked; + + logger.get.mockReturnValue(logger); + + return logger; +}; + +describe('SLO usage collector fetcher', () => { + let esServer: TestElasticsearchUtils; + let esClient: ElasticsearchClient; + let soClient: SavedObjectsClientContract; + let kibanaServer: TestKibanaUtils; + let sloRepository: SLORepository; + let loggerMock: jest.Mocked; + + beforeAll(async () => { + await createServers(); + }); + + afterAll(async () => { + await stopServers(); + }); + + describe('with some SLOs', () => { + beforeEach(async () => { + await Promise.all([ + sloRepository.create(createSLO({ indicator: createAPMTransactionErrorRateIndicator() })), + sloRepository.create(createSLO({ indicator: createAPMTransactionDurationIndicator() })), + sloRepository.create(createSLO({ indicator: createSyntheticsAvailabilityIndicator() })), + sloRepository.create(createSLO({ indicator: createKQLCustomIndicator() })), + sloRepository.create( + createSLOWithTimeslicesBudgetingMethod({ indicator: createMetricCustomIndicator() }) + ), + sloRepository.create( + createSLOWithTimeslicesBudgetingMethod({ indicator: createTimesliceMetricIndicator() }) + ), + sloRepository.create( + createSLO({ groupBy: ['host.name'], indicator: createKQLCustomIndicator() }) + ), + sloRepository.create( + createSLO({ groupBy: 'host.name', indicator: createKQLCustomIndicator() }) + ), + ]); + }); + + it('returns the correct metrics', async () => { + const results = await fetcher({ soClient, esClient }); + + expect(results.slo).toMatchInlineSnapshot(` + Object { + "by_budgeting_method": Object { + "occurrences": 6, + "timeslices": 2, + }, + "by_calendar_aligned_duration": Object {}, + "by_rolling_duration": Object { + "7d": 8, + }, + "by_sli_type": Object { + "sli.apm.transactionDuration": 1, + "sli.apm.transactionErrorRate": 1, + "sli.kql.custom": 3, + "sli.metric.custom": 1, + "sli.metric.timeslice": 1, + "sli.synthetics.availability": 1, + }, + "by_status": Object { + "disabled": 0, + "enabled": 8, + }, + "definitions": Object { + "total": 8, + "total_with_ccs": 0, + "total_with_groups": 2, + }, + "instances": Object { + "total": 0, + }, + "total": 8, + } + `); + }); + }); + + async function createServers() { + const { startES, startKibana } = createTestServers({ + adjustTimeout: jest.setTimeout, + settings: { + es: { + license: 'trial', + }, + kbn: { + cliArgs: { + oss: false, + }, + }, + }, + }); + + esServer = await startES(); + kibanaServer = await startKibana(); + + esClient = kibanaServer.coreStart.elasticsearch.client.asInternalUser; + soClient = new SavedObjectsClient( + kibanaServer.coreStart.savedObjects.createInternalRepository() + ); + loggerMock = createLoggerMock(); + + sloRepository = new KibanaSavedObjectsSLORepository(soClient, loggerMock); + } + + async function stopServers() { + if (kibanaServer) { + await kibanaServer.stop(); + } + if (esServer) { + await esServer.stop(); + } + + jest.clearAllMocks(); + } +}); diff --git a/x-pack/solutions/observability/plugins/slo/server/lib/collectors/register.ts b/x-pack/solutions/observability/plugins/slo/server/lib/collectors/register.ts index 59f364206a04d..394bf47b57840 100644 --- a/x-pack/solutions/observability/plugins/slo/server/lib/collectors/register.ts +++ b/x-pack/solutions/observability/plugins/slo/server/lib/collectors/register.ts @@ -21,20 +21,48 @@ export function registerSloUsageCollector(usageCollection?: UsageCollectionSetup total: { type: 'long', _meta: { - description: 'The total number of slos in the cluster', + description: 'The total number of SLOs in the cluster', + }, + }, + definitions: { + total: { + type: 'long', + _meta: { + description: 'The total number of SLO definitions in the cluster', + }, + }, + total_with_ccs: { + type: 'long', + _meta: { + description: 'The total number of SLO definitions using CCS in the cluster', + }, + }, + total_with_groups: { + type: 'long', + _meta: { + description: 'The total number of SLO definitions using groups in the cluster', + }, + }, + }, + instances: { + total: { + type: 'long', + _meta: { + description: 'The total number of SLO instances in the cluster', + }, }, }, by_status: { enabled: { type: 'long', _meta: { - description: 'The number of enabled slos in the cluster', + description: 'The number of enabled SLOs in the cluster', }, }, disabled: { type: 'long', _meta: { - description: 'The number of disabled slos in the cluster', + description: 'The number of disabled SLOs in the cluster', }, }, }, @@ -42,7 +70,7 @@ export function registerSloUsageCollector(usageCollection?: UsageCollectionSetup DYNAMIC_KEY: { type: 'long', _meta: { - description: 'The number of slos by sli type in the cluster', + description: 'The number of SLOs by sli type in the cluster', }, }, }, @@ -50,7 +78,7 @@ export function registerSloUsageCollector(usageCollection?: UsageCollectionSetup DYNAMIC_KEY: { type: 'long', _meta: { - description: 'The number of slos by rolling duration in the cluster', + description: 'The number of SLOs by rolling duration in the cluster', }, }, }, @@ -58,7 +86,7 @@ export function registerSloUsageCollector(usageCollection?: UsageCollectionSetup DYNAMIC_KEY: { type: 'long', _meta: { - description: 'The number of slos by calendar aligned duration in the cluster', + description: 'The number of SLOs by calendar aligned duration in the cluster', }, }, }, @@ -66,13 +94,13 @@ export function registerSloUsageCollector(usageCollection?: UsageCollectionSetup occurrences: { type: 'long', _meta: { - description: 'The number of slos by timeslices budgeting method in the cluster', + description: 'The number of SLOs by timeslices budgeting method in the cluster', }, }, timeslices: { type: 'long', _meta: { - description: 'The number of slos by occurrences budgeting method in the cluster', + description: 'The number of SLOs by occurrences budgeting method in the cluster', }, }, }, diff --git a/x-pack/solutions/observability/plugins/slo/server/lib/collectors/type.ts b/x-pack/solutions/observability/plugins/slo/server/lib/collectors/type.ts index fd0af37d78e45..74724023ca95b 100644 --- a/x-pack/solutions/observability/plugins/slo/server/lib/collectors/type.ts +++ b/x-pack/solutions/observability/plugins/slo/server/lib/collectors/type.ts @@ -7,7 +7,15 @@ export interface Usage { slo: { - total: number; + total: number; // deprecated + definitions: { + total: number; + total_with_ccs: number; + total_with_groups: number; + }; + instances: { + total: number; + }; by_status: { enabled: number; disabled: number; diff --git a/x-pack/solutions/observability/plugins/slo/tsconfig.json b/x-pack/solutions/observability/plugins/slo/tsconfig.json index e6cd1c2d890fc..f5da461434f32 100644 --- a/x-pack/solutions/observability/plugins/slo/tsconfig.json +++ b/x-pack/solutions/observability/plugins/slo/tsconfig.json @@ -101,5 +101,6 @@ "@kbn/discover-shared-plugin", "@kbn/server-route-repository-client", "@kbn/security-plugin-types-public", + "@kbn/core-test-helpers-kbn-server", ] }