Skip to content

Commit

Permalink
[RAC] turn off observability alerts as data writing in a more granula…
Browse files Browse the repository at this point in the history
…r way (elastic#119602)

* [RAC] turn off writing to disabled alerts indices

* fix error

* fix errors

* do not install component templates for disabled registration contexts

* add resource installer unit tests

* refactoring: disable installing index level resources in the rule data plugin service and not in the resourceInstaller

* refactor based on review comments

* update comment for isWriteEnabled method

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
2 people authored and TinLe committed Dec 22, 2021
1 parent e3d8aa8 commit 2f772df
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 12 deletions.
1 change: 1 addition & 0 deletions x-pack/plugins/rule_registry/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const config: PluginConfigDescriptor = {
deprecations: ({ deprecate, unused }) => [unused('unsafe.indexUpgrade.enabled')],
schema: schema.object({
write: schema.object({
disabledRegistrationContexts: schema.arrayOf(schema.string(), { defaultValue: [] }),
enabled: schema.boolean({ defaultValue: true }),
cache: schema.object({
enabled: schema.boolean({ defaultValue: true }),
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/rule_registry/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export class RuleRegistryPlugin
this.ruleDataService = new RuleDataService({
logger,
kibanaVersion,
disabledRegistrationContexts: this.config.write.disabledRegistrationContexts,
isWriteEnabled: this.config.write.enabled,
isWriterCacheEnabled: this.config.write.cache.enabled,
getClusterClient: async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* 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 { ResourceInstaller } from './resource_installer';
import { loggerMock } from '@kbn/logging/mocks';
import { AlertConsumers } from '@kbn/rule-data-utils/alerts_as_data_rbac';

import { Dataset } from './index_options';
import { IndexInfo } from './index_info';
import { elasticsearchServiceMock } from 'src/core/server/mocks';
import {
DEFAULT_ILM_POLICY_ID,
ECS_COMPONENT_TEMPLATE_NAME,
TECHNICAL_COMPONENT_TEMPLATE_NAME,
} from '../../common/assets';

describe('resourceInstaller', () => {
describe('if write is disabled', () => {
it('should not install common resources', async () => {
const mockClusterClient = elasticsearchServiceMock.createElasticsearchClient();
const getClusterClient = jest.fn(() => Promise.resolve(mockClusterClient));
const installer = new ResourceInstaller({
logger: loggerMock.create(),
isWriteEnabled: false,
disabledRegistrationContexts: [],
getResourceName: jest.fn(),
getClusterClient,
});
installer.installCommonResources();
expect(getClusterClient).not.toHaveBeenCalled();
});

it('should not install index level resources', () => {
const mockClusterClient = elasticsearchServiceMock.createElasticsearchClient();
const getClusterClient = jest.fn(() => Promise.resolve(mockClusterClient));

const installer = new ResourceInstaller({
logger: loggerMock.create(),
isWriteEnabled: false,
disabledRegistrationContexts: [],
getResourceName: jest.fn(),
getClusterClient,
});
const indexOptions = {
feature: AlertConsumers.LOGS,
registrationContext: 'observability.logs',
dataset: Dataset.alerts,
componentTemplateRefs: [],
componentTemplates: [
{
name: 'mappings',
},
],
};
const indexInfo = new IndexInfo({ indexOptions, kibanaVersion: '8.1.0' });

installer.installIndexLevelResources(indexInfo);
expect(mockClusterClient.cluster.putComponentTemplate).not.toHaveBeenCalled();
});
});

describe('if write is enabled', () => {
it('should install common resources', async () => {
const mockClusterClient = elasticsearchServiceMock.createElasticsearchClient();
const getClusterClient = jest.fn(() => Promise.resolve(mockClusterClient));
const getResourceNameMock = jest
.fn()
.mockReturnValueOnce(DEFAULT_ILM_POLICY_ID)
.mockReturnValueOnce(TECHNICAL_COMPONENT_TEMPLATE_NAME)
.mockReturnValueOnce(ECS_COMPONENT_TEMPLATE_NAME);
const installer = new ResourceInstaller({
logger: loggerMock.create(),
isWriteEnabled: true,
disabledRegistrationContexts: [],
getResourceName: getResourceNameMock,
getClusterClient,
});

await installer.installCommonResources();

expect(mockClusterClient.ilm.putLifecycle).toHaveBeenCalled();
expect(mockClusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(2);
expect(mockClusterClient.cluster.putComponentTemplate).toHaveBeenNthCalledWith(
1,
expect.objectContaining({ name: TECHNICAL_COMPONENT_TEMPLATE_NAME })
);
expect(mockClusterClient.cluster.putComponentTemplate).toHaveBeenNthCalledWith(
2,
expect.objectContaining({ name: ECS_COMPONENT_TEMPLATE_NAME })
);
});
it('should install index level resources', async () => {
const mockClusterClient = elasticsearchServiceMock.createElasticsearchClient();
const getClusterClient = jest.fn(() => Promise.resolve(mockClusterClient));
const installer = new ResourceInstaller({
logger: loggerMock.create(),
isWriteEnabled: true,
disabledRegistrationContexts: [],
getResourceName: jest.fn(),
getClusterClient,
});

const indexOptions = {
feature: AlertConsumers.LOGS,
registrationContext: 'observability.logs',
dataset: Dataset.alerts,
componentTemplateRefs: [],
componentTemplates: [
{
name: 'mappings',
},
],
};
const indexInfo = new IndexInfo({ indexOptions, kibanaVersion: '8.1.0' });

await installer.installIndexLevelResources(indexInfo);
expect(mockClusterClient.cluster.putComponentTemplate).toHaveBeenCalledWith(
expect.objectContaining({ name: '.alerts-observability.logs.alerts-mappings' })
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ interface ConstructorOptions {
getClusterClient: () => Promise<ElasticsearchClient>;
logger: Logger;
isWriteEnabled: boolean;
disabledRegistrationContexts: string[];
}

export class ResourceInstaller {
Expand All @@ -40,7 +41,6 @@ export class ResourceInstaller {
try {
const installResources = async (): Promise<void> => {
const { logger, isWriteEnabled } = this.options;

if (!isWriteEnabled) {
logger.info(`Write is disabled; not installing ${resources}`);
return;
Expand Down Expand Up @@ -113,7 +113,6 @@ export class ResourceInstaller {
public async installIndexLevelResources(indexInfo: IndexInfo): Promise<void> {
await this.installWithTimeout(`resources for index ${indexInfo.baseName}`, async () => {
const { componentTemplates, ilmPolicy } = indexInfo.indexOptions;

if (ilmPolicy != null) {
await this.createOrUpdateLifecyclePolicy({
name: indexInfo.getIlmPolicyName(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ export interface IRuleDataService {
getResourceName(relativeName: string): string;

/**
* If write is enabled, everything works as usual.
* If it's disabled, writing to all alerts-as-data indices will be disabled,
* If write is enabled for the specified registration context, everything works as usual.
* If it's disabled, writing to the registration context's alerts-as-data indices will be disabled,
* and also Elasticsearch resources associated with the indices will not be
* installed.
*/
isWriteEnabled(): boolean;
isWriteEnabled(registrationContext: string): boolean;

/**
* If writer cache is enabled (the default), the writer will be cached
Expand Down Expand Up @@ -83,6 +83,7 @@ interface ConstructorOptions {
kibanaVersion: string;
isWriteEnabled: boolean;
isWriterCacheEnabled: boolean;
disabledRegistrationContexts: string[];
}

export class RuleDataService implements IRuleDataService {
Expand All @@ -95,11 +96,11 @@ export class RuleDataService implements IRuleDataService {
constructor(private readonly options: ConstructorOptions) {
this.indicesByBaseName = new Map();
this.indicesByFeatureId = new Map();

this.resourceInstaller = new ResourceInstaller({
getResourceName: (name) => this.getResourceName(name),
getClusterClient: options.getClusterClient,
logger: options.logger,
disabledRegistrationContexts: options.disabledRegistrationContexts,
isWriteEnabled: options.isWriteEnabled,
});

Expand All @@ -115,8 +116,12 @@ export class RuleDataService implements IRuleDataService {
return joinWithDash(this.getResourcePrefix(), relativeName);
}

public isWriteEnabled(): boolean {
return this.options.isWriteEnabled;
public isWriteEnabled(registrationContext: string): boolean {
return this.options.isWriteEnabled && !this.isRegistrationContextDisabled(registrationContext);
}

public isRegistrationContextDisabled(registrationContext: string): boolean {
return this.options.disabledRegistrationContexts.includes(registrationContext);
}

/**
Expand Down Expand Up @@ -150,7 +155,7 @@ export class RuleDataService implements IRuleDataService {
'Rule data service is not initialized. Make sure to call initializeService() in the rule registry plugin setup phase'
);
}

const { registrationContext } = indexOptions;
const indexInfo = new IndexInfo({ indexOptions, kibanaVersion: this.options.kibanaVersion });

const indicesAssociatedWithFeature = this.indicesByFeatureId.get(indexOptions.feature) ?? [];
Expand All @@ -173,8 +178,9 @@ export class RuleDataService implements IRuleDataService {
if (isLeft(result)) {
return result;
}

await this.resourceInstaller.installIndexLevelResources(indexInfo);
if (!this.isRegistrationContextDisabled(registrationContext)) {
await this.resourceInstaller.installIndexLevelResources(indexInfo);
}

const clusterClient = await this.options.getClusterClient();
return right(clusterClient);
Expand All @@ -195,7 +201,7 @@ export class RuleDataService implements IRuleDataService {
return new RuleDataClient({
indexInfo,
resourceInstaller: this.resourceInstaller,
isWriteEnabled: this.isWriteEnabled(),
isWriteEnabled: this.isWriteEnabled(registrationContext),
isWriterCacheEnabled: this.isWriterCacheEnabled(),
waitUntilReadyForReading,
waitUntilReadyForWriting,
Expand Down

0 comments on commit 2f772df

Please sign in to comment.