Skip to content

Commit

Permalink
Add rule model versions in alerting (elastic#171927)
Browse files Browse the repository at this point in the history
Towards: elastic#166967

This PR adds `ruleModelVersion` to saved object registry and
`latestRuleVersion` to ruleTypeRegistry.
These new assets will be used in a follow-on PR for skipping the rule
task executions when there is version mismatch.

POC for the issue: elastic#167128

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
ersin-erdal and kibanamachine authored Nov 28, 2023
1 parent 45e88fe commit 59eb614
Show file tree
Hide file tree
Showing 12 changed files with 168 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
Object {
"action": "cc93fe2c0c76e57c2568c63170e05daea897c136",
"action_task_params": "96e27e7f4e8273ffcd87060221e2b75e81912dd5",
"alert": "dc710bc17dfc98a9a703d388569abccce5f8bf07",
"alert": "3a67d3f1db80af36bd57aaea47ecfef87e43c58f",
"api_key_pending_invalidation": "1399e87ca37b3d3a65d269c924eda70726cfe886",
"apm-custom-dashboards": "b67128f78160c288bd7efe25b2da6e2afd5e82fc",
"apm-indices": "8a2d68d415a4b542b26b0d292034a28ffac6fed4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ describe('createGetAlertIndicesAliasFn', () => {
licensing: licensingMock.createSetup(),
minimumScheduleInterval: { value: '1m', enforce: false },
inMemoryMetrics,
latestRuleVersion: 1,
};
const registry = new RuleTypeRegistry(ruleTypeRegistryParams);
registry.register({
Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/alerting/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ import {
} from './types';
import { registerAlertingUsageCollector } from './usage';
import { initializeAlertingTelemetry, scheduleAlertingTelemetry } from './usage/task';
import { setupSavedObjects } from './saved_objects';
import { setupSavedObjects, getLatestRuleVersion } from './saved_objects';
import {
initializeApiKeyInvalidator,
scheduleApiKeyInvalidatorTask,
Expand Down Expand Up @@ -305,6 +305,7 @@ export class AlertingPlugin {
alertsService: this.alertsService,
minimumScheduleInterval: this.config.rules.minimumScheduleInterval,
inMemoryMetrics: this.inMemoryMetrics,
latestRuleVersion: getLatestRuleVersion(),
});
this.ruleTypeRegistry = ruleTypeRegistry;

Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/alerting/server/rule_type_registry.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const createRuleTypeRegistryMock = () => {
list: jest.fn(),
getAllTypes: jest.fn(),
ensureRuleTypeEnabled: jest.fn(),
getLatestRuleVersion: jest.fn(),
};
return mocked;
};
Expand Down
11 changes: 11 additions & 0 deletions x-pack/plugins/alerting/server/rule_type_registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ beforeEach(() => {
licensing: licensingMock.createSetup(),
minimumScheduleInterval: { value: '1m', enforce: false },
inMemoryMetrics,
latestRuleVersion: 1,
};
});

Expand Down Expand Up @@ -912,6 +913,16 @@ describe('Create Lifecycle', () => {
).toThrowErrorMatchingInlineSnapshot(`"Fail"`);
});
});

describe('getLatestRuleVersion', () => {
test('should return the latest rule version', async () => {
const ruleTypeRegistry = new RuleTypeRegistry({
...ruleTypeRegistryParams,
latestRuleVersion: 5,
});
expect(ruleTypeRegistry.getLatestRuleVersion()).toBe(5);
});
});
});

function ruleTypeWithVariables<ActionGroupIds extends string>(
Expand Down
12 changes: 10 additions & 2 deletions x-pack/plugins/alerting/server/rule_type_registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { Logger } from '@kbn/core/server';
import { LicensingPluginSetup } from '@kbn/licensing-plugin/server';
import { RunContext, TaskManagerSetupContract } from '@kbn/task-manager-plugin/server';
import { stateSchemaByVersion } from '@kbn/alerting-state-types';
import { rawRuleSchema } from './raw_rule_schema';
import { TaskRunnerFactory } from './task_runner';
import {
RuleType,
Expand All @@ -40,6 +39,7 @@ import { AlertingRulesConfig } from '.';
import { AlertsService } from './alerts_service/alerts_service';
import { getRuleTypeIdValidLegacyConsumers } from './rule_type_registry_deprecated_consumers';
import { AlertingConfig } from './config';
import { rawRuleSchemaV1 } from './saved_objects/schemas/raw_rule';

export interface ConstructorOptions {
config: AlertingConfig;
Expand All @@ -51,6 +51,7 @@ export interface ConstructorOptions {
minimumScheduleInterval: AlertingRulesConfig['minimumScheduleInterval'];
inMemoryMetrics: InMemoryMetrics;
alertsService: AlertsService | null;
latestRuleVersion: number;
}

export interface RegistryRuleType
Expand Down Expand Up @@ -160,6 +161,7 @@ export class RuleTypeRegistry {
private readonly licensing: LicensingPluginSetup;
private readonly inMemoryMetrics: InMemoryMetrics;
private readonly alertsService: AlertsService | null;
private readonly latestRuleVersion: number;

constructor({
config,
Expand All @@ -171,6 +173,7 @@ export class RuleTypeRegistry {
minimumScheduleInterval,
inMemoryMetrics,
alertsService,
latestRuleVersion,
}: ConstructorOptions) {
this.config = config;
this.logger = logger;
Expand All @@ -181,6 +184,7 @@ export class RuleTypeRegistry {
this.minimumScheduleInterval = minimumScheduleInterval;
this.inMemoryMetrics = inMemoryMetrics;
this.alertsService = alertsService;
this.latestRuleVersion = latestRuleVersion;
}

public has(id: string) {
Expand Down Expand Up @@ -311,7 +315,7 @@ export class RuleTypeRegistry {
spaceId: schema.string(),
consumer: schema.maybe(schema.string()),
}),
indirectParamsSchema: rawRuleSchema,
indirectParamsSchema: rawRuleSchemaV1,
},
});

Expand Down Expand Up @@ -434,6 +438,10 @@ export class RuleTypeRegistry {
public getAllTypes(): string[] {
return [...this.ruleTypes.keys()];
}

public getLatestRuleVersion() {
return this.latestRuleVersion;
}
}

function normalizedActionVariables(actionVariables: RuleType['actionVariables']) {
Expand Down
3 changes: 3 additions & 0 deletions x-pack/plugins/alerting/server/saved_objects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ import { getImportWarnings } from './get_import_warnings';
import { isRuleExportable } from './is_rule_exportable';
import { RuleTypeRegistry } from '../rule_type_registry';
export { partiallyUpdateAlert } from './partially_update_alert';
export { getLatestRuleVersion, getMinimumCompatibleVersion } from './rule_model_versions';
import {
RULES_SETTINGS_SAVED_OBJECT_TYPE,
MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE,
} from '../../common';
import { ruleModelVersions } from './rule_model_versions';

// Use caution when removing items from this array! Any field which has
// ever existed in the rule SO must be included in this array to prevent
Expand Down Expand Up @@ -106,6 +108,7 @@ export function setupSavedObjects(
return isRuleExportable(ruleSavedObject, ruleTypeRegistry, logger);
},
},
modelVersions: ruleModelVersions,
});

savedObjects.registerType({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ beforeEach(() => {
licensing: licensingMock.createSetup(),
minimumScheduleInterval: { value: '1m', enforce: false },
inMemoryMetrics,
latestRuleVersion: 1,
};
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* 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 {
CustomSavedObjectsModelVersionMap,
getLatestRuleVersion,
getMinimumCompatibleVersion,
} from './rule_model_versions';
import { schema } from '@kbn/config-schema';
import { RawRule } from '../types';

describe('rule model versions', () => {
const ruleModelVersions: CustomSavedObjectsModelVersionMap = {
'1': {
changes: [],
schemas: {
create: schema.object({
name: schema.string(),
}),
},
isCompatibleWithPreviousVersion: (rawRule) => true,
},
'2': {
changes: [],
schemas: {
create: schema.object({
name: schema.string(),
}),
},
isCompatibleWithPreviousVersion: (rawRule) => false,
},
'3': {
changes: [],
schemas: {
create: schema.object({
name: schema.string(),
}),
},
isCompatibleWithPreviousVersion: (rawRule) => rawRule.name === 'test',
},
'4': {
changes: [],
schemas: {
create: schema.object({
name: schema.string(),
}),
},
isCompatibleWithPreviousVersion: (rawRule) => rawRule.name === 'test',
},
};

const rawRule = { name: 'test' } as RawRule;
const mismatchingRawRule = { enabled: true } as RawRule;

describe('getMinimumCompatibleVersion', () => {
it('should return the minimum compatible version for the matching rawRule', () => {
expect(getMinimumCompatibleVersion(ruleModelVersions, 1, rawRule)).toBe(1);
expect(getMinimumCompatibleVersion(ruleModelVersions, 2, rawRule)).toBe(2);
expect(getMinimumCompatibleVersion(ruleModelVersions, 3, rawRule)).toBe(2);
expect(getMinimumCompatibleVersion(ruleModelVersions, 4, rawRule)).toBe(2);
});
it('should return the minimum compatible version for the mismatching rawRule', () => {
expect(getMinimumCompatibleVersion(ruleModelVersions, 3, mismatchingRawRule)).toBe(3);
expect(getMinimumCompatibleVersion(ruleModelVersions, 4, mismatchingRawRule)).toBe(4);
});
});

describe('getLatestRuleVersion', () => {
it('should return the latest rule model version', () => {
expect(getLatestRuleVersion()).toBe(1);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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 {
SavedObjectsModelVersion,
SavedObjectsModelVersionMap,
} from '@kbn/core-saved-objects-server';
import { RawRule } from '../types';
import { rawRuleSchemaV1 } from './schemas/raw_rule';

interface CustomSavedObjectsModelVersion extends SavedObjectsModelVersion {
isCompatibleWithPreviousVersion: (param: RawRule) => boolean;
}

export interface CustomSavedObjectsModelVersionMap extends SavedObjectsModelVersionMap {
[modelVersion: string]: CustomSavedObjectsModelVersion;
}

export const ruleModelVersions: CustomSavedObjectsModelVersionMap = {
'1': {
changes: [],
schemas: {
create: rawRuleSchemaV1,
},
isCompatibleWithPreviousVersion: (rawRule) => true,
},
};

export const getLatestRuleVersion = () => Math.max(...Object.keys(ruleModelVersions).map(Number));

export function getMinimumCompatibleVersion(
modelVersions: CustomSavedObjectsModelVersionMap,
version: number,
rawRule: RawRule
): number {
if (version === 1) {
return 1;
}

if (modelVersions[version].isCompatibleWithPreviousVersion(rawRule)) {
return getMinimumCompatibleVersion(modelVersions, version - 1, rawRule);
}

return version;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* 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.
*/

export { rawRuleSchema as rawRuleSchemaV1 } from './v1';
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ const rawRuleAlertsFilterSchema = schema.object({
key: schema.maybe(schema.string()),
params: schema.maybe(schema.recordOf(schema.string(), schema.any())), // better type?
value: schema.maybe(schema.string()),
field: schema.maybe(schema.string()),
}),
$state: schema.maybe(
schema.object({
Expand Down Expand Up @@ -209,6 +210,7 @@ const rawRuleActionSchema = schema.object({
})
),
alertsFilter: schema.maybe(rawRuleAlertsFilterSchema),
useAlertDataForTemplate: schema.maybe(schema.boolean()),
});

export const rawRuleSchema = schema.object({
Expand Down Expand Up @@ -266,5 +268,6 @@ export const rawRuleSchema = schema.object({
severity: schema.maybe(schema.string()),
})
),
params: schema.recordOf(schema.string(), schema.any()),
params: schema.recordOf(schema.string(), schema.maybe(schema.any())),
typeVersion: schema.maybe(schema.number()),
});

0 comments on commit 59eb614

Please sign in to comment.