Skip to content

Commit

Permalink
[SLO] skip resource installation when current version matches resourc…
Browse files Browse the repository at this point in the history
…e version (elastic#174371)

Fixes elastic#172488

## Summary

Avoids re-installing SLO resources, including ingest pipeline, component
templates, and index templates, if the version of the resource has
changed.

### Testing
You can test this PR by manually changing the slo ingest pipeline or
component templates, either via api or directly in the UI. For example:
1. Navigate app/management/data/index_management/component_templates
2. In the search bar, type slo to find slo component templates (one of
the components listed below)
3. Click on a component template and make a change, for example,
changing a mapping.
4. Stop you Kibana instance
5. Restart your Kibana instance
6. Check the component template edited. Ensure the edit is persisted. 

You can repeat this with all resources, if desired, including:

- Index template: .slo-observability.sli
- Index template: .slo-observability.summary
- Ingest pipeline: .slo-observability.sli.pipeline-v*
- Component template: .slo-observability.sli-mappings
- Component template: .slo-observability.sli-settings
- Component template: .slo-observability.summary-mappings
- Component template: .slo-observability.summary-settings
  • Loading branch information
dominiqueclarke authored Jan 17, 2024
1 parent eff0c23 commit b4fa818
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import type { ClusterPutComponentTemplateRequest } from '@elastic/elasticsearch/lib/api/types';
import { SLO_RESOURCES_VERSION } from '../../../common/slo/constants';

export const getSLOMappingsTemplate = (name: string) => ({
export const getSLOMappingsTemplate = (name: string): ClusterPutComponentTemplateRequest => ({
name,
template: {
mappings: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import type { ClusterPutComponentTemplateRequest } from '@elastic/elasticsearch/lib/api/types';
import { SLO_RESOURCES_VERSION } from '../../../common/slo/constants';

export const getSLOSummaryMappingsTemplate = (name: string) => ({
export const getSLOSummaryMappingsTemplate = (
name: string
): ClusterPutComponentTemplateRequest => ({
name,
template: {
mappings: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import type { IngestPipeline } from '@elastic/elasticsearch/lib/api/types';
import { elasticsearchServiceMock } from '@kbn/core/server/mocks';
import { loggerMock } from '@kbn/logging-mocks';
import {
Expand All @@ -15,13 +16,49 @@ import {
SLO_SUMMARY_COMPONENT_TEMPLATE_MAPPINGS_NAME,
SLO_SUMMARY_COMPONENT_TEMPLATE_SETTINGS_NAME,
SLO_SUMMARY_INDEX_TEMPLATE_NAME,
SLO_RESOURCES_VERSION,
} from '../../../common/slo/constants';
import { DefaultResourceInstaller } from './resource_installer';

describe('resourceInstaller', () => {
it('installs the common resources', async () => {
it('installs the common resources when there is a version mismatch', async () => {
const mockClusterClient = elasticsearchServiceMock.createElasticsearchClient();
mockClusterClient.indices.getIndexTemplate.mockResponseOnce({ index_templates: [] });
mockClusterClient.cluster.getComponentTemplate.mockResponse({
component_templates: [
{
name: SLO_INDEX_TEMPLATE_NAME,
component_template: {
_meta: {
version: 2,
},
template: {
settings: {},
},
},
},
],
});
mockClusterClient.indices.getIndexTemplate.mockResponse({
index_templates: [
{
name: SLO_INDEX_TEMPLATE_NAME,
index_template: {
index_patterns: SLO_INDEX_TEMPLATE_NAME,
composed_of: [SLO_SUMMARY_COMPONENT_TEMPLATE_MAPPINGS_NAME],
_meta: {
version: 2,
},
},
},
],
});
mockClusterClient.ingest.getPipeline.mockResponse({
[SLO_INGEST_PIPELINE_NAME]: {
_meta: {
version: 2,
},
} as IngestPipeline,
});
const installer = new DefaultResourceInstaller(mockClusterClient, loggerMock.create());

await installer.ensureCommonResourcesInstalled();
Expand Down Expand Up @@ -59,4 +96,52 @@ describe('resourceInstaller', () => {
expect.objectContaining({ id: SLO_INGEST_PIPELINE_NAME })
);
});

it('does not install the common resources when there is a version match', async () => {
const mockClusterClient = elasticsearchServiceMock.createElasticsearchClient();
mockClusterClient.cluster.getComponentTemplate.mockResponse({
component_templates: [
{
name: SLO_INDEX_TEMPLATE_NAME,
component_template: {
_meta: {
version: SLO_RESOURCES_VERSION,
},
template: {
settings: {},
},
},
},
],
});
mockClusterClient.indices.getIndexTemplate.mockResponse({
index_templates: [
{
name: SLO_INDEX_TEMPLATE_NAME,
index_template: {
index_patterns: SLO_INDEX_TEMPLATE_NAME,
composed_of: [SLO_SUMMARY_COMPONENT_TEMPLATE_MAPPINGS_NAME],
_meta: {
version: SLO_RESOURCES_VERSION,
},
},
},
],
});
mockClusterClient.ingest.getPipeline.mockResponse({
[SLO_INGEST_PIPELINE_NAME]: {
_meta: {
version: SLO_RESOURCES_VERSION,
},
} as IngestPipeline,
});
const installer = new DefaultResourceInstaller(mockClusterClient, loggerMock.create());

await installer.ensureCommonResourcesInstalled();

expect(mockClusterClient.cluster.putComponentTemplate).not.toHaveBeenCalled();
expect(mockClusterClient.indices.putIndexTemplate).not.toHaveBeenCalled();

expect(mockClusterClient.ingest.putPipeline).not.toHaveBeenCalled();
});
});
111 changes: 104 additions & 7 deletions x-pack/plugins/observability/server/services/slo/resource_installer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import type {
ClusterPutComponentTemplateRequest,
IndicesPutIndexTemplateRequest,
IngestPutPipelineRequest,
} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
IngestPipeline,
Metadata,
} from '@elastic/elasticsearch/lib/api/types';
import type { ElasticsearchClient, Logger } from '@kbn/core/server';
import { getSLOMappingsTemplate } from '../../assets/component_templates/slo_mappings_template';
import { getSLOSettingsTemplate } from '../../assets/component_templates/slo_settings_template';
Expand Down Expand Up @@ -39,6 +41,8 @@ export interface ResourceInstaller {
ensureCommonResourcesInstalled(): Promise<void>;
}

type IngestPipelineWithMetadata = IngestPipeline & { _meta?: Metadata };

export class DefaultResourceInstaller implements ResourceInstaller {
constructor(private esClient: ElasticsearchClient, private logger: Logger) {}

Expand Down Expand Up @@ -92,18 +96,46 @@ export class DefaultResourceInstaller implements ResourceInstaller {
}

private async createOrUpdateComponentTemplate(template: ClusterPutComponentTemplateRequest) {
this.logger.info(`Installing SLO component template [${template.name}]`);
return this.execute(() => this.esClient.cluster.putComponentTemplate(template));
const currentVersion = await fetchComponentTemplateVersion(
template.name,
this.logger,
this.esClient
);
if (template._meta?.version && currentVersion === template._meta.version) {
this.logger.info(`SLO component template found with version [${template._meta.version}]`);
} else {
this.logger.info(`Installing SLO component template [${template.name}]`);
return this.execute(() => this.esClient.cluster.putComponentTemplate(template));
}
}

private async createOrUpdateIndexTemplate(template: IndicesPutIndexTemplateRequest) {
this.logger.info(`Installing SLO index template [${template.name}]`);
return this.execute(() => this.esClient.indices.putIndexTemplate(template));
const currentVersion = await fetchIndexTemplateVersion(
template.name,
this.logger,
this.esClient
);

if (template._meta?.version && currentVersion === template._meta.version) {
this.logger.info(`SLO index template found with version [${template._meta.version}]`);
} else {
this.logger.info(`Installing SLO index template [${template.name}]`);
return this.execute(() => this.esClient.indices.putIndexTemplate(template));
}
}

private async createOrUpdateIngestPipelineTemplate(template: IngestPutPipelineRequest) {
this.logger.info(`Installing SLO ingest pipeline [${template.id}]`);
return this.execute(() => this.esClient.ingest.putPipeline(template));
const currentVersion = await fetchIngestPipelineVersion(
template.id,
this.logger,
this.esClient
);
if (template._meta?.version && currentVersion === template._meta.version) {
this.logger.info(`SLO ingest pipeline found with version [${template._meta.version}]`);
} else {
this.logger.info(`Installing SLO ingest pipeline [${template.id}]`);
return this.execute(() => this.esClient.ingest.putPipeline(template));
}
}

private async createIndex(indexName: string) {
Expand All @@ -120,3 +152,68 @@ export class DefaultResourceInstaller implements ResourceInstaller {
return await retryTransientEsErrors(esCall, { logger: this.logger });
}
}

async function fetchComponentTemplateVersion(
name: string,
logger: Logger,
esClient: ElasticsearchClient
) {
const getTemplateRes = await retryTransientEsErrors(
() =>
esClient.cluster.getComponentTemplate(
{
name,
},
{
ignore: [404],
}
),
{ logger }
);

return getTemplateRes?.component_templates?.[0]?.component_template?._meta?.version || null;
}

async function fetchIndexTemplateVersion(
name: string,
logger: Logger,
esClient: ElasticsearchClient
) {
const getTemplateRes = await retryTransientEsErrors(
() =>
esClient.indices.getIndexTemplate(
{
name,
},
{
ignore: [404],
}
),
{ logger }
);

return getTemplateRes?.index_templates?.[0]?.index_template?._meta?.version || null;
}

async function fetchIngestPipelineVersion(
id: string,
logger: Logger,
esClient: ElasticsearchClient
) {
const getPipelineRes = await retryTransientEsErrors<
Record<string, IngestPipelineWithMetadata | undefined>
>(
() =>
esClient.ingest.getPipeline(
{
id,
},
{
ignore: [404],
}
),
{ logger }
);

return getPipelineRes?.[id]?._meta?.version || null;
}

0 comments on commit b4fa818

Please sign in to comment.