[RAM] Introduce maxScheduledPerMinute rule circuit breaker and route (#164791)

## Summary
Resolves: https://github.com/elastic/kibana/issues/162262

This PR is the backend changes to add a circuit breaker
`xpack.alerting.rules.maxScheduledPerMinute` to both serverless and
other environments that limits the number of rules to 400 runs / minute
and 10000 runs / minute, respectively. There will be another PR to
follow this one that gives the user UI hints when creating/editing rules
that go over this limit.

This circuit breaker check is applied to the following routes:
- Create Rule
- Update Rule
- Enable Rule
- Bulk Enable Rule
- Bulk Edit Rule

Also adds a new route: `/internal/alerting/rules/_schedule_frequency` to
get the current total schedules per minute (of enabled rules) and the
remaining interval allotment.

### Checklist
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: lcawl <lcawley@elastic.co>
Co-authored-by: Xavier Mouligneau <xavier.mouligneau@elastic.co>
This commit is contained in:
Jiawei Wu 2023-09-06 09:13:36 -07:00 committed by GitHub
parent 1a9bb19e57
commit 456f47f3ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
77 changed files with 1773 additions and 138 deletions

View file

@ -171,6 +171,7 @@ enabled:
- x-pack/test/alerting_api_integration/security_and_spaces/group1/config.ts - x-pack/test/alerting_api_integration/security_and_spaces/group1/config.ts
- x-pack/test/alerting_api_integration/security_and_spaces/group2/config.ts - x-pack/test/alerting_api_integration/security_and_spaces/group2/config.ts
- x-pack/test/alerting_api_integration/security_and_spaces/group3/config.ts - x-pack/test/alerting_api_integration/security_and_spaces/group3/config.ts
- x-pack/test/alerting_api_integration/security_and_spaces/group3/config_with_schedule_circuit_breaker.ts
- x-pack/test/alerting_api_integration/security_and_spaces/group2/config_non_dedicated_task_runner.ts - x-pack/test/alerting_api_integration/security_and_spaces/group2/config_non_dedicated_task_runner.ts
- x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/config.ts - x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/config.ts
- x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/config.ts - x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/config.ts

View file

@ -117,6 +117,7 @@ xpack.alerting.rules.run.ruleTypeOverrides:
- id: siem.indicatorRule - id: siem.indicatorRule
timeout: 1m timeout: 1m
xpack.alerting.rules.minimumScheduleInterval.enforce: true xpack.alerting.rules.minimumScheduleInterval.enforce: true
xpack.alerting.rules.maxScheduledPerMinute: 400
xpack.actions.run.maxAttempts: 10 xpack.actions.run.maxAttempts: 10
# Disables ESQL in advanced settings (hides it from the UI) # Disables ESQL in advanced settings (hides it from the UI)

View file

@ -301,6 +301,9 @@ Specifies whether to skip writing alerts and scheduling actions if rule
processing was cancelled due to a timeout. Default: `true`. This setting can be processing was cancelled due to a timeout. Default: `true`. This setting can be
overridden by individual rule types. overridden by individual rule types.
`xpack.alerting.rules.maxScheduledPerMinute` {ess-icon}::
Specifies the maximum number of rules to run per minute. Default: 10000
`xpack.alerting.rules.minimumScheduleInterval.value` {ess-icon}:: `xpack.alerting.rules.minimumScheduleInterval.value` {ess-icon}::
Specifies the minimum schedule interval for rules. This minimum is applied to all rules created or updated after you set this value. Specifies the minimum schedule interval for rules. This minimum is applied to all rules created or updated after you set this value.
The time is formatted as a number and a time unit (`s`, `m`, `h`, or `d`). The time is formatted as a number and a time unit (`s`, `m`, `h`, or `d`).

View file

@ -10,6 +10,7 @@ import {
formatDuration, formatDuration,
getDurationNumberInItsUnit, getDurationNumberInItsUnit,
getDurationUnitValue, getDurationUnitValue,
convertDurationToFrequency,
} from './parse_duration'; } from './parse_duration';
test('parses seconds', () => { test('parses seconds', () => {
@ -180,3 +181,26 @@ test('getDurationUnitValue hours', () => {
const result = getDurationUnitValue('100h'); const result = getDurationUnitValue('100h');
expect(result).toEqual('h'); expect(result).toEqual('h');
}); });
test('convertDurationToFrequency converts duration', () => {
let result = convertDurationToFrequency('1m');
expect(result).toEqual(1);
result = convertDurationToFrequency('5m');
expect(result).toEqual(0.2);
result = convertDurationToFrequency('10s');
expect(result).toEqual(6);
result = convertDurationToFrequency('1s');
expect(result).toEqual(60);
});
test('convertDurationToFrequency throws when duration is invalid', () => {
expect(() => convertDurationToFrequency('0d')).toThrowErrorMatchingInlineSnapshot(
`"Invalid duration \\"0d\\". Durations must be of the form {number}x. Example: 5s, 5m, 5h or 5d\\""`
);
});
test('convertDurationToFrequency throws when denomination is 0', () => {
expect(() => convertDurationToFrequency('1s', 0)).toThrowErrorMatchingInlineSnapshot(
`"Invalid denomination value: value cannot be 0"`
);
});

View file

@ -10,6 +10,8 @@ const MINUTES_REGEX = /^[1-9][0-9]*m$/;
const HOURS_REGEX = /^[1-9][0-9]*h$/; const HOURS_REGEX = /^[1-9][0-9]*h$/;
const DAYS_REGEX = /^[1-9][0-9]*d$/; const DAYS_REGEX = /^[1-9][0-9]*d$/;
const MS_PER_MINUTE = 60 * 1000;
// parse an interval string '{digit*}{s|m|h|d}' into milliseconds // parse an interval string '{digit*}{s|m|h|d}' into milliseconds
export function parseDuration(duration: string): number { export function parseDuration(duration: string): number {
const parsed = parseInt(duration, 10); const parsed = parseInt(duration, 10);
@ -43,6 +45,19 @@ export function formatDuration(duration: string, fullUnit?: boolean): string {
); );
} }
export function convertDurationToFrequency(
duration: string,
denomination: number = MS_PER_MINUTE
): number {
const durationInMs = parseDuration(duration);
if (denomination === 0) {
throw new Error(`Invalid denomination value: value cannot be 0`);
}
const intervalInDenominationUnits = durationInMs / denomination;
return 1 / intervalInDenominationUnits;
}
export function getDurationNumberInItsUnit(duration: string): number { export function getDurationNumberInItsUnit(duration: string): number {
return parseInt(duration.replace(/[^0-9.]/g, ''), 10); return parseInt(duration.replace(/[^0-9.]/g, ''), 10);
} }

View file

@ -0,0 +1,26 @@
/*
* 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 {
getScheduleFrequencyResponseSchema,
getScheduleFrequencyResponseBodySchema,
} from './schemas/latest';
export type {
GetScheduleFrequencyResponse,
GetScheduleFrequencyResponseBody,
} from './types/latest';
export {
getScheduleFrequencyResponseSchema as getScheduleFrequencyResponseSchemaV1,
getScheduleFrequencyResponseBodySchema as getScheduleFrequencyResponseBodySchemaV1,
} from './schemas/v1';
export type {
GetScheduleFrequencyResponse as GetScheduleFrequencyResponseV1,
GetScheduleFrequencyResponseBody as GetScheduleFrequencyResponseBodyV1,
} from './types/v1';

View file

@ -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 * from './v1';

View file

@ -0,0 +1,17 @@
/*
* 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 { schema } from '@kbn/config-schema';
export const getScheduleFrequencyResponseBodySchema = schema.object({
total_scheduled_per_minute: schema.number(),
remaining_schedules_per_minute: schema.number(),
});
export const getScheduleFrequencyResponseSchema = schema.object({
body: getScheduleFrequencyResponseBodySchema,
});

View file

@ -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 * from './v1';

View file

@ -0,0 +1,15 @@
/*
* 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 type { TypeOf } from '@kbn/config-schema';
import { getScheduleFrequencyResponseSchemaV1, getScheduleFrequencyResponseBodySchemaV1 } from '..';
export type GetScheduleFrequencyResponseBody = TypeOf<
typeof getScheduleFrequencyResponseBodySchemaV1
>;
export type GetScheduleFrequencyResponse = TypeOf<typeof getScheduleFrequencyResponseSchemaV1>;

View file

@ -10,7 +10,11 @@ import { omit } from 'lodash';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { AlertConsumers } from '@kbn/rule-data-utils'; import { AlertConsumers } from '@kbn/rule-data-utils';
import { RulesClient, ConstructorOptions } from '../../../../rules_client/rules_client'; import { RulesClient, ConstructorOptions } from '../../../../rules_client/rules_client';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../../../authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../../../authorization/alerting_authorization.mock';
@ -56,7 +60,12 @@ jest.mock('uuid', () => {
return { v4: () => `${uuid++}` }; return { v4: () => `${uuid++}` };
}); });
jest.mock('../get_schedule_frequency', () => ({
validateScheduleLimit: jest.fn(),
}));
const { isSnoozeActive } = jest.requireMock('../../../../lib/snooze/is_snooze_active'); const { isSnoozeActive } = jest.requireMock('../../../../lib/snooze/is_snooze_active');
const { validateScheduleLimit } = jest.requireMock('../get_schedule_frequency');
const taskManager = taskManagerMock.createStart(); const taskManager = taskManagerMock.createStart();
const ruleTypeRegistry = ruleTypeRegistryMock.create(); const ruleTypeRegistry = ruleTypeRegistryMock.create();
@ -65,6 +74,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertingAuthorizationMock.create(); const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditLoggerMock.create(); const auditLogger = auditLoggerMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v8.2.0'; const kibanaVersion = 'v8.2.0';
const createAPIKeyMock = jest.fn(); const createAPIKeyMock = jest.fn();
@ -82,11 +92,13 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: createAPIKeyMock, createAPIKey: createAPIKeyMock,
logger: loggingSystemMock.create().get(), logger: loggingSystemMock.create().get(),
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),
kibanaVersion, kibanaVersion,
auditLogger, auditLogger,
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
isAuthenticationTypeAPIKey: isAuthenticationTypeApiKeyMock, isAuthenticationTypeAPIKey: isAuthenticationTypeApiKeyMock,
getAuthenticationAPIKey: getAuthenticationApiKeyMock, getAuthenticationAPIKey: getAuthenticationApiKeyMock,
@ -2495,6 +2507,66 @@ describe('bulkEdit()', () => {
`Error updating rule with ID "${existingDecryptedRule.id}": the interval 10m is longer than the action frequencies` `Error updating rule with ID "${existingDecryptedRule.id}": the interval 10m is longer than the action frequencies`
); );
}); });
test('should only validate schedule limit if schedule is being modified', async () => {
unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({
saved_objects: [
{
id: '1',
type: 'alert',
attributes: {
enabled: true,
tags: ['foo', 'test-1'],
alertTypeId: 'myType',
schedule: { interval: '1m' },
consumer: 'myApp',
scheduledTaskId: 'task-123',
executionStatus: {
lastExecutionDate: '2019-02-12T21:01:22.479Z',
status: 'pending',
},
params: {},
throttle: null,
notifyWhen: null,
actions: [],
revision: 0,
},
references: [],
version: '123',
},
],
});
await rulesClient.bulkEdit({
filter: '',
operations: [
{
field: 'tags',
operation: 'add',
value: ['test-1'],
},
],
});
expect(validateScheduleLimit).toHaveBeenCalledTimes(0);
await rulesClient.bulkEdit({
operations: [
{
field: 'schedule',
operation: 'set',
value: { interval: '2m' },
},
{
field: 'tags',
operation: 'add',
value: ['test-1'],
},
],
});
expect(validateScheduleLimit).toHaveBeenCalledTimes(1);
});
}); });
describe('paramsModifier', () => { describe('paramsModifier', () => {

View file

@ -77,6 +77,11 @@ import {
transformRuleDomainToRuleAttributes, transformRuleDomainToRuleAttributes,
transformRuleDomainToRule, transformRuleDomainToRule,
} from '../../transforms'; } from '../../transforms';
import { validateScheduleLimit } from '../get_schedule_frequency';
const isValidInterval = (interval: string | undefined): interval is string => {
return interval !== undefined;
};
export const bulkEditFieldsToExcludeFromRevisionUpdates = new Set(['snoozeSchedule', 'apiKey']); export const bulkEditFieldsToExcludeFromRevisionUpdates = new Set(['snoozeSchedule', 'apiKey']);
@ -286,8 +291,16 @@ async function bulkEditRulesOcc<Params extends RuleParams>(
const errors: BulkOperationError[] = []; const errors: BulkOperationError[] = [];
const apiKeysMap: ApiKeysMap = new Map(); const apiKeysMap: ApiKeysMap = new Map();
const username = await context.getUserName(); const username = await context.getUserName();
const prevInterval: string[] = [];
for await (const response of rulesFinder.find()) { for await (const response of rulesFinder.find()) {
const intervals = response.saved_objects
.filter((rule) => rule.attributes.enabled)
.map((rule) => rule.attributes.schedule?.interval)
.filter(isValidInterval);
prevInterval.concat(intervals);
await pMap( await pMap(
response.saved_objects, response.saved_objects,
async (rule: SavedObjectsFindResult<RuleAttributes>) => async (rule: SavedObjectsFindResult<RuleAttributes>) =>
@ -308,9 +321,44 @@ async function bulkEditRulesOcc<Params extends RuleParams>(
} }
await rulesFinder.close(); await rulesFinder.close();
const updatedInterval = rules
.filter((rule) => rule.attributes.enabled)
.map((rule) => rule.attributes.schedule?.interval)
.filter(isValidInterval);
try {
if (operations.some((operation) => operation.field === 'schedule')) {
await validateScheduleLimit({
context,
prevInterval,
updatedInterval,
});
}
} catch (error) {
return {
apiKeysToInvalidate: Array.from(apiKeysMap.values())
.filter((value) => value.newApiKey)
.map((value) => value.newApiKey as string),
resultSavedObjects: [],
rules: [],
errors: rules.map((rule) => ({
message: `Failed to bulk edit rule - ${error.message}`,
rule: {
id: rule.id,
name: rule.attributes.name || 'n/a',
},
})),
skipped: [],
};
}
const { result, apiKeysToInvalidate } = const { result, apiKeysToInvalidate } =
rules.length > 0 rules.length > 0
? await saveBulkUpdatedRules(context, rules, apiKeysMap) ? await saveBulkUpdatedRules({
context,
rules,
apiKeysMap,
})
: { : {
result: { saved_objects: [] }, result: { saved_objects: [] },
apiKeysToInvalidate: [], apiKeysToInvalidate: [],
@ -821,11 +869,15 @@ function updateAttributes(
}; };
} }
async function saveBulkUpdatedRules( async function saveBulkUpdatedRules({
context: RulesClientContext, context,
rules: Array<SavedObjectsBulkUpdateObject<RuleAttributes>>, rules,
apiKeysMap: ApiKeysMap apiKeysMap,
) { }: {
context: RulesClientContext;
rules: Array<SavedObjectsBulkUpdateObject<RuleAttributes>>;
apiKeysMap: ApiKeysMap;
}) {
const apiKeysToInvalidate: string[] = []; const apiKeysToInvalidate: string[] = [];
let result; let result;
try { try {

View file

@ -8,7 +8,11 @@
import { schema } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema';
import { CreateRuleParams } from './create_rule'; import { CreateRuleParams } from './create_rule';
import { RulesClient, ConstructorOptions } from '../../../../rules_client'; import { RulesClient, ConstructorOptions } from '../../../../rules_client';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../../../authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../../../authorization/alerting_authorization.mock';
@ -43,6 +47,10 @@ jest.mock('uuid', () => {
return { v4: () => `${uuid++}` }; return { v4: () => `${uuid++}` };
}); });
jest.mock('../get_schedule_frequency', () => ({
validateScheduleLimit: jest.fn(),
}));
const taskManager = taskManagerMock.createStart(); const taskManager = taskManagerMock.createStart();
const ruleTypeRegistry = ruleTypeRegistryMock.create(); const ruleTypeRegistry = ruleTypeRegistryMock.create();
const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
@ -50,6 +58,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertingAuthorizationMock.create(); const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditLoggerMock.create(); const auditLogger = auditLoggerMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v8.0.0'; const kibanaVersion = 'v8.0.0';
const rulesClientParams: jest.Mocked<ConstructorOptions> = { const rulesClientParams: jest.Mocked<ConstructorOptions> = {
@ -63,11 +72,13 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: jest.fn(), createAPIKey: jest.fn(),
logger: loggingSystemMock.create().get(), logger: loggingSystemMock.create().get(),
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),
kibanaVersion, kibanaVersion,
auditLogger, auditLogger,
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
isAuthenticationTypeAPIKey: jest.fn(), isAuthenticationTypeAPIKey: jest.fn(),
getAuthenticationAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(),

View file

@ -36,6 +36,7 @@ import { RuleAttributes } from '../../../../data/rule/types';
import type { CreateRuleData } from './types'; import type { CreateRuleData } from './types';
import { createRuleDataSchema } from './schemas'; import { createRuleDataSchema } from './schemas';
import { createRuleSavedObject } from '../../../../rules_client/lib'; import { createRuleSavedObject } from '../../../../rules_client/lib';
import { validateScheduleLimit } from '../get_schedule_frequency';
export interface CreateRuleOptions { export interface CreateRuleOptions {
id?: string; id?: string;
@ -60,6 +61,12 @@ export async function createRule<Params extends RuleParams = never>(
try { try {
createRuleDataSchema.validate(data); createRuleDataSchema.validate(data);
if (data.enabled) {
await validateScheduleLimit({
context,
updatedInterval: data.schedule.interval,
});
}
} catch (error) { } catch (error) {
throw Boom.badRequest(`Error validating create data - ${error.message}`); throw Boom.badRequest(`Error validating create data - ${error.message}`);
} }

View file

@ -0,0 +1,233 @@
/*
* 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 { validateScheduleLimit } from './get_schedule_frequency';
import { RulesClient, ConstructorOptions } from '../../../../rules_client';
import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../../../authorization/alerting_authorization.mock';
import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks';
import { actionsAuthorizationMock } from '@kbn/actions-plugin/server/mocks';
import { AlertingAuthorization } from '../../../../authorization/alerting_authorization';
import { ActionsAuthorization } from '@kbn/actions-plugin/server';
import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks';
const taskManager = taskManagerMock.createStart();
const ruleTypeRegistry = ruleTypeRegistryMock.create();
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditLoggerMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v8.0.0';
const rulesClientParams: jest.Mocked<ConstructorOptions> = {
taskManager,
ruleTypeRegistry,
unsecuredSavedObjectsClient,
authorization: authorization as unknown as AlertingAuthorization,
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
spaceId: 'default',
namespace: 'default',
getUserName: jest.fn(),
createAPIKey: jest.fn(),
logger: loggingSystemMock.create().get(),
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(),
getEventLogClient: jest.fn(),
kibanaVersion,
auditLogger,
maxScheduledPerMinute: 100,
minimumScheduleInterval: { value: '1m', enforce: false },
isAuthenticationTypeAPIKey: jest.fn(),
getAuthenticationAPIKey: jest.fn(),
};
const getMockAggregationResult = (
intervalAggs: Array<{
interval: string;
count: number;
}>
) => {
return {
aggregations: {
schedule_intervals: {
buckets: intervalAggs.map(({ interval, count }) => ({
key: interval,
doc_count: count,
})),
},
},
page: 1,
per_page: 20,
total: 1,
saved_objects: [],
};
};
describe('getScheduleFrequency()', () => {
beforeEach(() => {
internalSavedObjectsRepository.find.mockResolvedValue(
getMockAggregationResult([
{ interval: '1m', count: 1 },
{ interval: '1m', count: 2 },
{ interval: '1m', count: 3 },
{ interval: '5m', count: 5 },
{ interval: '5m', count: 10 },
{ interval: '5m', count: 15 },
])
);
});
afterEach(() => {
jest.clearAllMocks();
});
test('should return the correct schedule frequency results', async () => {
const rulesClient = new RulesClient(rulesClientParams);
const result = await rulesClient.getScheduleFrequency();
// (1 * 6) + (1/5 * 30) = 12
expect(result.totalScheduledPerMinute).toEqual(12);
// 100 - 88
expect(result.remainingSchedulesPerMinute).toEqual(88);
});
test('should handle empty bucket correctly', async () => {
internalSavedObjectsRepository.find.mockResolvedValue({
page: 1,
per_page: 20,
total: 1,
saved_objects: [],
});
const rulesClient = new RulesClient(rulesClientParams);
const result = await rulesClient.getScheduleFrequency();
expect(result.totalScheduledPerMinute).toEqual(0);
expect(result.remainingSchedulesPerMinute).toEqual(100);
});
test('should handle malformed schedule interval correctly', async () => {
internalSavedObjectsRepository.find.mockResolvedValue(
getMockAggregationResult([
{ interval: '1m', count: 1 },
{ interval: '1m', count: 2 },
{ interval: '1m', count: 3 },
{ interval: '5m', count: 5 },
{ interval: '5m', count: 10 },
{ interval: '5m', count: 15 },
{ interval: 'invalid', count: 15 },
])
);
const rulesClient = new RulesClient(rulesClientParams);
const result = await rulesClient.getScheduleFrequency();
expect(result.totalScheduledPerMinute).toEqual(12);
expect(result.remainingSchedulesPerMinute).toEqual(88);
});
test('should not go below 0 for remaining schedules', async () => {
internalSavedObjectsRepository.find.mockResolvedValue(
getMockAggregationResult([
{ interval: '1m', count: 1 },
{ interval: '1m', count: 2 },
{ interval: '1m', count: 3 },
{ interval: '5m', count: 5 },
{ interval: '5m', count: 10 },
{ interval: '5m', count: 15 },
])
);
const rulesClient = new RulesClient({
...rulesClientParams,
maxScheduledPerMinute: 10,
});
const result = await rulesClient.getScheduleFrequency();
expect(result.totalScheduledPerMinute).toEqual(12);
expect(result.remainingSchedulesPerMinute).toEqual(0);
});
});
describe('validateScheduleLimit', () => {
const context = {
...rulesClientParams,
maxScheduledPerMinute: 5,
minimumScheduleIntervalInMs: 1000,
fieldsToExcludeFromPublicApi: [],
};
beforeEach(() => {
internalSavedObjectsRepository.find.mockResolvedValue(
getMockAggregationResult([{ interval: '1m', count: 2 }])
);
});
afterEach(() => {
jest.clearAllMocks();
});
test('should not throw if the updated interval does not exceed limits', () => {
return expect(
validateScheduleLimit({
context,
updatedInterval: ['1m', '1m'],
})
).resolves.toBe(undefined);
});
test('should throw if the updated interval exceeds limits', () => {
return expect(
validateScheduleLimit({
context,
updatedInterval: ['1m', '1m', '1m', '2m'],
})
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Run limit reached: The rule has 3.5 runs per minute; there are only 3 runs per minute available."`
);
});
test('should not throw if previous interval was modified to be under the limit', () => {
internalSavedObjectsRepository.find.mockResolvedValue(
getMockAggregationResult([{ interval: '1m', count: 6 }])
);
return expect(
validateScheduleLimit({
context,
prevInterval: ['1m', '1m'],
updatedInterval: ['2m', '2m'],
})
).resolves.toBe(undefined);
});
test('should throw if the previous interval was modified to exceed the limit', () => {
internalSavedObjectsRepository.find.mockResolvedValue(
getMockAggregationResult([{ interval: '1m', count: 5 }])
);
return expect(
validateScheduleLimit({
context,
prevInterval: ['1m'],
updatedInterval: ['30s'],
})
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Run limit reached: The rule has 2 runs per minute; there are only 1 runs per minute available."`
);
});
});

View file

@ -0,0 +1,115 @@
/*
* 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 { RulesClientContext } from '../../../../rules_client/types';
import { RuleDomain } from '../../types';
import { convertDurationToFrequency } from '../../../../../common/parse_duration';
import { GetScheduleFrequencyResult } from './types';
import { getSchemaFrequencyResultSchema } from './schema';
export interface SchedulesIntervalAggregationResult {
schedule_intervals: {
buckets: Array<{
key: string;
doc_count: number;
}>;
};
}
const convertIntervalToFrequency = (context: RulesClientContext, schedule: string) => {
let scheduleFrequency = 0;
try {
// Normalize the interval (period) in terms of minutes
scheduleFrequency = convertDurationToFrequency(schedule);
} catch (e) {
context.logger.warn(
`Failed to parse rule schedule interval for schedule frequency calculation: ${e.message}`
);
}
return scheduleFrequency;
};
export const getScheduleFrequency = async (
context: RulesClientContext
): Promise<GetScheduleFrequencyResult> => {
const response = await context.internalSavedObjectsRepository.find<
RuleDomain,
SchedulesIntervalAggregationResult
>({
type: 'alert',
filter: 'alert.attributes.enabled: true',
namespaces: ['*'],
aggs: {
schedule_intervals: {
terms: {
field: 'alert.attributes.schedule.interval',
},
},
},
});
const buckets = response.aggregations?.schedule_intervals.buckets ?? [];
const totalScheduledPerMinute = buckets.reduce((result, { key, doc_count: occurrence }) => {
const scheduleFrequency = convertIntervalToFrequency(context, key);
// Sum up all of the frequencies, since this is an aggregation.
return result + scheduleFrequency * occurrence;
}, 0);
const result = {
totalScheduledPerMinute,
remainingSchedulesPerMinute: Math.max(
context.maxScheduledPerMinute - totalScheduledPerMinute,
0
),
};
try {
getSchemaFrequencyResultSchema.validate(result);
} catch (e) {
context.logger.warn(`Error validating rule schedules per minute: ${e}`);
}
return result;
};
interface ValidateScheduleLimitParams {
context: RulesClientContext;
prevInterval?: string | string[];
updatedInterval: string | string[];
}
export const validateScheduleLimit = async (params: ValidateScheduleLimitParams) => {
const { context, prevInterval = [], updatedInterval = [] } = params;
const prevIntervalArray = Array.isArray(prevInterval) ? prevInterval : [prevInterval];
const updatedIntervalArray = Array.isArray(updatedInterval) ? updatedInterval : [updatedInterval];
const prevSchedulePerMinute = prevIntervalArray.reduce((result, interval) => {
const scheduleFrequency = convertIntervalToFrequency(context, interval);
return result + scheduleFrequency;
}, 0);
const updatedSchedulesPerMinute = updatedIntervalArray.reduce((result, interval) => {
const scheduleFrequency = convertIntervalToFrequency(context, interval);
return result + scheduleFrequency;
}, 0);
const { remainingSchedulesPerMinute } = await getScheduleFrequency(context);
// Compute the new remaining schedules per minute if we are editing rules.
// So we add back the edited schedules, since we assume those are being edited.
const computedRemainingSchedulesPerMinute = remainingSchedulesPerMinute + prevSchedulePerMinute;
if (computedRemainingSchedulesPerMinute < updatedSchedulesPerMinute) {
throw new Error(
`Run limit reached: The rule has ${updatedSchedulesPerMinute} runs per minute; there are only ${computedRemainingSchedulesPerMinute} runs per minute available.`
);
}
};

View file

@ -0,0 +1,10 @@
/*
* 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 type { GetScheduleFrequencyResult } from './types';
export { getScheduleFrequency, validateScheduleLimit } from './get_schedule_frequency';

View file

@ -0,0 +1,13 @@
/*
* 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 { schema } from '@kbn/config-schema';
export const getSchemaFrequencyResultSchema = schema.object({
totalScheduledPerMinute: schema.number({ min: 0 }),
remainingSchedulesPerMinute: schema.number({ min: 0 }),
});

View file

@ -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 { getSchemaFrequencyResultSchema } from './get_schedule_frequency_result_schema';

View file

@ -0,0 +1,11 @@
/*
* 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 { TypeOf } from '@kbn/config-schema';
import { getSchemaFrequencyResultSchema } from '../schema';
export type GetScheduleFrequencyResult = TypeOf<typeof getSchemaFrequencyResultSchema>;

View file

@ -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 type { GetScheduleFrequencyResult } from './get_schedule_frequency_result';

View file

@ -23,6 +23,7 @@ describe('config validation', () => {
}, },
"maxEphemeralActionsPerAlert": 10, "maxEphemeralActionsPerAlert": 10,
"rules": Object { "rules": Object {
"maxScheduledPerMinute": 10000,
"minimumScheduleInterval": Object { "minimumScheduleInterval": Object {
"enforce": false, "enforce": false,
"value": "1m", "value": "1m",

View file

@ -38,6 +38,7 @@ const rulesSchema = schema.object({
}), }),
enforce: schema.boolean({ defaultValue: false }), // if enforce is false, only warnings will be shown enforce: schema.boolean({ defaultValue: false }), // if enforce is false, only warnings will be shown
}), }),
maxScheduledPerMinute: schema.number({ defaultValue: 10000, max: 10000, min: 0 }),
run: schema.object({ run: schema.object({
timeout: schema.maybe(schema.string({ validate: validateDurationSchema })), timeout: schema.maybe(schema.string({ validate: validateDurationSchema })),
actions: schema.object({ actions: schema.object({
@ -70,7 +71,10 @@ export const configSchema = schema.object({
export type AlertingConfig = TypeOf<typeof configSchema>; export type AlertingConfig = TypeOf<typeof configSchema>;
export type RulesConfig = TypeOf<typeof rulesSchema>; export type RulesConfig = TypeOf<typeof rulesSchema>;
export type AlertingRulesConfig = Pick<AlertingConfig['rules'], 'minimumScheduleInterval'> & { export type AlertingRulesConfig = Pick<
AlertingConfig['rules'],
'minimumScheduleInterval' | 'maxScheduledPerMinute'
> & {
isUsingSecurity: boolean; isUsingSecurity: boolean;
}; };
export type ActionsConfig = RulesConfig['run']['actions']; export type ActionsConfig = RulesConfig['run']['actions'];

View file

@ -139,6 +139,7 @@ describe('Alerting Plugin', () => {
await waitForSetupComplete(setupMocks); await waitForSetupComplete(setupMocks);
expect(setupContract.getConfig()).toEqual({ expect(setupContract.getConfig()).toEqual({
maxScheduledPerMinute: 10000,
isUsingSecurity: false, isUsingSecurity: false,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
}); });

View file

@ -415,7 +415,7 @@ export class AlertingPlugin {
}, },
getConfig: () => { getConfig: () => {
return { return {
...pick(this.config.rules, 'minimumScheduleInterval'), ...pick(this.config.rules, ['minimumScheduleInterval', 'maxScheduledPerMinute']),
isUsingSecurity: this.licenseState ? !!this.licenseState.getIsSecurityEnabled() : false, isUsingSecurity: this.licenseState ? !!this.licenseState.getIsSecurityEnabled() : false,
}; };
}, },
@ -481,6 +481,7 @@ export class AlertingPlugin {
taskManager: plugins.taskManager, taskManager: plugins.taskManager,
securityPluginSetup: security, securityPluginSetup: security,
securityPluginStart: plugins.security, securityPluginStart: plugins.security,
internalSavedObjectsRepository: core.savedObjects.createInternalRepository(['alert']),
encryptedSavedObjectsClient, encryptedSavedObjectsClient,
spaceIdToNamespace, spaceIdToNamespace,
getSpaceId(request: KibanaRequest) { getSpaceId(request: KibanaRequest) {
@ -492,6 +493,7 @@ export class AlertingPlugin {
authorization: alertingAuthorizationClientFactory, authorization: alertingAuthorizationClientFactory,
eventLogger: this.eventLogger, eventLogger: this.eventLogger,
minimumScheduleInterval: this.config.rules.minimumScheduleInterval, minimumScheduleInterval: this.config.rules.minimumScheduleInterval,
maxScheduledPerMinute: this.config.rules.maxScheduledPerMinute,
}); });
rulesSettingsClientFactory.initialize({ rulesSettingsClientFactory.initialize({

View file

@ -47,6 +47,7 @@ import { cloneRuleRoute } from './clone_rule';
import { getFlappingSettingsRoute } from './get_flapping_settings'; import { getFlappingSettingsRoute } from './get_flapping_settings';
import { updateFlappingSettingsRoute } from './update_flapping_settings'; import { updateFlappingSettingsRoute } from './update_flapping_settings';
import { getRuleTagsRoute } from './get_rule_tags'; import { getRuleTagsRoute } from './get_rule_tags';
import { getScheduleFrequencyRoute } from './rule/apis/get_schedule_frequency';
import { createMaintenanceWindowRoute } from './maintenance_window/create_maintenance_window'; import { createMaintenanceWindowRoute } from './maintenance_window/create_maintenance_window';
import { getMaintenanceWindowRoute } from './maintenance_window/get_maintenance_window'; import { getMaintenanceWindowRoute } from './maintenance_window/get_maintenance_window';
@ -129,4 +130,5 @@ export function defineRoutes(opts: RouteOptions) {
registerRulesValueSuggestionsRoute(router, licenseState, config$!); registerRulesValueSuggestionsRoute(router, licenseState, config$!);
registerFieldsRoute(router, licenseState); registerFieldsRoute(router, licenseState);
bulkGetMaintenanceWindowRoute(router, licenseState); bulkGetMaintenanceWindowRoute(router, licenseState);
getScheduleFrequencyRoute(router, licenseState);
} }

View file

@ -0,0 +1,59 @@
/*
* 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 { getScheduleFrequencyRoute } from './get_schedule_frequency_route';
import { httpServiceMock } from '@kbn/core/server/mocks';
import { licenseStateMock } from '../../../../lib/license_state.mock';
import { mockHandlerArguments } from '../../../_mock_handler_arguments';
import { rulesClientMock } from '../../../../rules_client.mock';
const rulesClient = rulesClientMock.create();
jest.mock('../../../../lib/license_api_access', () => ({
verifyApiAccess: jest.fn(),
}));
beforeEach(() => {
jest.resetAllMocks();
});
describe('getScheduleFrequencyRoute', () => {
it('gets the schedule frequency limit and remaining allotment', async () => {
const licenseState = licenseStateMock.create();
const router = httpServiceMock.createRouter();
getScheduleFrequencyRoute(router, licenseState);
const [config, handler] = router.get.mock.calls[0];
expect(config.path).toBe('/internal/alerting/rules/_schedule_frequency');
expect(config).toMatchInlineSnapshot(`
Object {
"path": "/internal/alerting/rules/_schedule_frequency",
"validate": Object {},
}
`);
rulesClient.getScheduleFrequency.mockResolvedValueOnce({
totalScheduledPerMinute: 9000,
remainingSchedulesPerMinute: 1000,
});
const [context, req, res] = mockHandlerArguments({ rulesClient }, {}, ['ok']);
await handler(context, req, res);
expect(rulesClient.getScheduleFrequency).toHaveBeenCalledTimes(1);
expect(res.ok).toHaveBeenCalledWith({
body: {
total_scheduled_per_minute: 9000,
remaining_schedules_per_minute: 1000,
},
});
});
});

View file

@ -0,0 +1,38 @@
/*
* 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 { IRouter } from '@kbn/core/server';
import { ILicenseState } from '../../../../lib';
import { verifyAccessAndContext } from '../../../lib';
import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../../../types';
import { GetScheduleFrequencyResponseV1 } from '../../../../../common/routes/rule/apis/get_schedule_frequency';
import { transformGetScheduleFrequencyResultV1 } from './transforms';
export const getScheduleFrequencyRoute = (
router: IRouter<AlertingRequestHandlerContext>,
licenseState: ILicenseState
) => {
router.get(
{
path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/_schedule_frequency`,
validate: {},
},
router.handleLegacyErrors(
verifyAccessAndContext(licenseState, async (context, req, res) => {
const rulesClient = (await context.alerting).getRulesClient();
const scheduleFrequencyResult = await rulesClient.getScheduleFrequency();
const response: GetScheduleFrequencyResponseV1 = {
body: transformGetScheduleFrequencyResultV1(scheduleFrequencyResult),
};
return res.ok(response);
})
)
);
};

View file

@ -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 { getScheduleFrequencyRoute } from './get_schedule_frequency_route';

View file

@ -0,0 +1,10 @@
/*
* 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 { transformGetScheduleFrequencyResult } from './transform_get_schedule_frequency_result/latest';
export { transformGetScheduleFrequencyResult as transformGetScheduleFrequencyResultV1 } from './transform_get_schedule_frequency_result/v1';

View file

@ -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 * from './v1';

View file

@ -0,0 +1,18 @@
/*
* 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 type { GetScheduleFrequencyResponseBodyV1 } from '../../../../../../../common/routes/rule/apis/get_schedule_frequency';
import type { GetScheduleFrequencyResult } from '../../../../../../application/rule/methods/get_schedule_frequency';
export const transformGetScheduleFrequencyResult = (
result: GetScheduleFrequencyResult
): GetScheduleFrequencyResponseBodyV1 => {
return {
total_scheduled_per_minute: result.totalScheduledPerMinute,
remaining_schedules_per_minute: result.remainingSchedulesPerMinute,
};
};

View file

@ -52,6 +52,7 @@ const createRulesClientMock = () => {
runSoon: jest.fn(), runSoon: jest.fn(),
clone: jest.fn(), clone: jest.fn(),
getAlertFromRaw: jest.fn(), getAlertFromRaw: jest.fn(),
getScheduleFrequency: jest.fn(),
}; };
return mocked; return mocked;
}; };

View file

@ -5,7 +5,11 @@
* 2.0. * 2.0.
*/ */
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
@ -24,6 +28,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertingAuthorizationMock.create(); const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v8.0.0'; const kibanaVersion = 'v8.0.0';
const rulesClientParams: jest.Mocked<RulesClientContext> = { const rulesClientParams: jest.Mocked<RulesClientContext> = {
@ -36,10 +41,12 @@ const rulesClientParams: jest.Mocked<RulesClientContext> = {
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: jest.fn(), createAPIKey: jest.fn(),
logger: loggingSystemMock.create().get(), logger: loggingSystemMock.create().get(),
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),
kibanaVersion, kibanaVersion,
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
minimumScheduleIntervalInMs: 1, minimumScheduleIntervalInMs: 1,
fieldsToExcludeFromPublicApi: [], fieldsToExcludeFromPublicApi: [],

View file

@ -4,9 +4,10 @@
* 2.0; you may not use this file except in compliance with the Elastic License * 2.0; you may not use this file except in compliance with the Elastic License
* 2.0. * 2.0.
*/ */
import pMap from 'p-map'; import pMap from 'p-map';
import { KueryNode, nodeBuilder } from '@kbn/es-query'; import { KueryNode, nodeBuilder } from '@kbn/es-query';
import { SavedObjectsBulkUpdateObject } from '@kbn/core/server'; import { SavedObjectsBulkUpdateObject, SavedObjectsFindResult } from '@kbn/core/server';
import { withSpan } from '@kbn/apm-utils'; import { withSpan } from '@kbn/apm-utils';
import { Logger } from '@kbn/core/server'; import { Logger } from '@kbn/core/server';
import { TaskManagerStartContract, TaskStatus } from '@kbn/task-manager-plugin/server'; import { TaskManagerStartContract, TaskStatus } from '@kbn/task-manager-plugin/server';
@ -28,6 +29,7 @@ import {
migrateLegacyActions, migrateLegacyActions,
} from '../lib'; } from '../lib';
import { RulesClientContext, BulkOperationError, BulkOptions } from '../types'; import { RulesClientContext, BulkOperationError, BulkOptions } from '../types';
import { validateScheduleLimit } from '../../application/rule/methods/get_schedule_frequency';
const getShouldScheduleTask = async ( const getShouldScheduleTask = async (
context: RulesClientContext, context: RulesClientContext,
@ -121,116 +123,136 @@ const bulkEnableRulesWithOCC = async (
) )
); );
const rulesFinderRules: Array<SavedObjectsFindResult<RawRule>> = [];
const rulesToEnable: Array<SavedObjectsBulkUpdateObject<RawRule>> = []; const rulesToEnable: Array<SavedObjectsBulkUpdateObject<RawRule>> = [];
const errors: BulkOperationError[] = []; const errors: BulkOperationError[] = [];
const ruleNameToRuleIdMapping: Record<string, string> = {}; const ruleNameToRuleIdMapping: Record<string, string> = {};
const username = await context.getUserName(); const username = await context.getUserName();
let scheduleValidationError = '';
await withSpan( await withSpan(
{ name: 'Get rules, collect them and their attributes', type: 'rules' }, { name: 'Get rules, collect them and their attributes', type: 'rules' },
async () => { async () => {
for await (const response of rulesFinder.find()) { for await (const response of rulesFinder.find()) {
await pMap(response.saved_objects, async (rule) => { rulesFinderRules.push(...response.saved_objects);
try {
if (rule.attributes.actions.length) {
try {
await context.actionsAuthorization.ensureAuthorized({ operation: 'execute' });
} catch (error) {
throw Error(`Rule not authorized for bulk enable - ${error.message}`);
}
}
if (rule.attributes.name) {
ruleNameToRuleIdMapping[rule.id] = rule.attributes.name;
}
const migratedActions = await migrateLegacyActions(context, {
ruleId: rule.id,
actions: rule.attributes.actions,
references: rule.references,
attributes: rule.attributes,
});
const updatedAttributes = updateMeta(context, {
...rule.attributes,
...(!rule.attributes.apiKey &&
(await createNewAPIKeySet(context, {
id: rule.attributes.alertTypeId,
ruleName: rule.attributes.name,
username,
shouldUpdateApiKey: true,
}))),
...(migratedActions.hasLegacyActions
? {
actions: migratedActions.resultedActions,
throttle: undefined,
notifyWhen: undefined,
}
: {}),
enabled: true,
updatedBy: username,
updatedAt: new Date().toISOString(),
executionStatus: {
status: 'pending',
lastDuration: 0,
lastExecutionDate: new Date().toISOString(),
error: null,
warning: null,
},
});
const shouldScheduleTask = await getShouldScheduleTask(
context,
rule.attributes.scheduledTaskId
);
let scheduledTaskId;
if (shouldScheduleTask) {
const scheduledTask = await scheduleTask(context, {
id: rule.id,
consumer: rule.attributes.consumer,
ruleTypeId: rule.attributes.alertTypeId,
schedule: rule.attributes.schedule as IntervalSchedule,
throwOnConflict: false,
});
scheduledTaskId = scheduledTask.id;
}
rulesToEnable.push({
...rule,
attributes: {
...updatedAttributes,
...(scheduledTaskId ? { scheduledTaskId } : undefined),
},
...(migratedActions.hasLegacyActions
? { references: migratedActions.resultedReferences }
: {}),
});
context.auditLogger?.log(
ruleAuditEvent({
action: RuleAuditAction.ENABLE,
outcome: 'unknown',
savedObject: { type: 'alert', id: rule.id },
})
);
} catch (error) {
errors.push({
message: error.message,
rule: {
id: rule.id,
name: rule.attributes?.name,
},
});
context.auditLogger?.log(
ruleAuditEvent({
action: RuleAuditAction.ENABLE,
error,
})
);
}
});
} }
await rulesFinder.close(); await rulesFinder.close();
const updatedInterval = rulesFinderRules
.filter((rule) => !rule.attributes.enabled)
.map((rule) => rule.attributes.schedule?.interval);
try {
await validateScheduleLimit({
context,
updatedInterval,
});
} catch (error) {
scheduleValidationError = `Error validating enable rule data - ${error.message}`;
}
await pMap(rulesFinderRules, async (rule) => {
try {
if (scheduleValidationError) {
throw Error(scheduleValidationError);
}
if (rule.attributes.actions.length) {
try {
await context.actionsAuthorization.ensureAuthorized({ operation: 'execute' });
} catch (error) {
throw Error(`Rule not authorized for bulk enable - ${error.message}`);
}
}
if (rule.attributes.name) {
ruleNameToRuleIdMapping[rule.id] = rule.attributes.name;
}
const migratedActions = await migrateLegacyActions(context, {
ruleId: rule.id,
actions: rule.attributes.actions,
references: rule.references,
attributes: rule.attributes,
});
const updatedAttributes = updateMeta(context, {
...rule.attributes,
...(!rule.attributes.apiKey &&
(await createNewAPIKeySet(context, {
id: rule.attributes.alertTypeId,
ruleName: rule.attributes.name,
username,
shouldUpdateApiKey: true,
}))),
...(migratedActions.hasLegacyActions
? {
actions: migratedActions.resultedActions,
throttle: undefined,
notifyWhen: undefined,
}
: {}),
enabled: true,
updatedBy: username,
updatedAt: new Date().toISOString(),
executionStatus: {
status: 'pending',
lastDuration: 0,
lastExecutionDate: new Date().toISOString(),
error: null,
warning: null,
},
});
const shouldScheduleTask = await getShouldScheduleTask(
context,
rule.attributes.scheduledTaskId
);
let scheduledTaskId;
if (shouldScheduleTask) {
const scheduledTask = await scheduleTask(context, {
id: rule.id,
consumer: rule.attributes.consumer,
ruleTypeId: rule.attributes.alertTypeId,
schedule: rule.attributes.schedule as IntervalSchedule,
throwOnConflict: false,
});
scheduledTaskId = scheduledTask.id;
}
rulesToEnable.push({
...rule,
attributes: {
...updatedAttributes,
...(scheduledTaskId ? { scheduledTaskId } : undefined),
},
...(migratedActions.hasLegacyActions
? { references: migratedActions.resultedReferences }
: {}),
});
context.auditLogger?.log(
ruleAuditEvent({
action: RuleAuditAction.ENABLE,
outcome: 'unknown',
savedObject: { type: 'alert', id: rule.id },
})
);
} catch (error) {
errors.push({
message: error.message,
rule: {
id: rule.id,
name: rule.attributes?.name,
},
});
context.auditLogger?.log(
ruleAuditEvent({
action: RuleAuditAction.ENABLE,
error,
})
);
}
});
} }
); );

View file

@ -4,6 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License * 2.0; you may not use this file except in compliance with the Elastic License
* 2.0. * 2.0.
*/ */
import Boom from '@hapi/boom';
import type { SavedObjectReference } from '@kbn/core/server'; import type { SavedObjectReference } from '@kbn/core/server';
import { TaskStatus } from '@kbn/task-manager-plugin/server'; import { TaskStatus } from '@kbn/task-manager-plugin/server';
import { RawRule, IntervalSchedule } from '../../types'; import { RawRule, IntervalSchedule } from '../../types';
@ -13,6 +14,7 @@ import { retryIfConflicts } from '../../lib/retry_if_conflicts';
import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events';
import { RulesClientContext } from '../types'; import { RulesClientContext } from '../types';
import { updateMeta, createNewAPIKeySet, scheduleTask, migrateLegacyActions } from '../lib'; import { updateMeta, createNewAPIKeySet, scheduleTask, migrateLegacyActions } from '../lib';
import { validateScheduleLimit } from '../../application/rule/methods/get_schedule_frequency';
export async function enable(context: RulesClientContext, { id }: { id: string }): Promise<void> { export async function enable(context: RulesClientContext, { id }: { id: string }): Promise<void> {
return await retryIfConflicts( return await retryIfConflicts(
@ -46,6 +48,15 @@ async function enableWithOCC(context: RulesClientContext, { id }: { id: string }
references = alert.references; references = alert.references;
} }
try {
await validateScheduleLimit({
context,
updatedInterval: attributes.schedule.interval,
});
} catch (error) {
throw Boom.badRequest(`Error validating enable rule data - ${error.message}`);
}
try { try {
await context.authorization.ensureAuthorized({ await context.authorization.ensureAuthorized({
ruleTypeId: attributes.alertTypeId, ruleTypeId: attributes.alertTypeId,

View file

@ -33,6 +33,7 @@ import {
createNewAPIKeySet, createNewAPIKeySet,
migrateLegacyActions, migrateLegacyActions,
} from '../lib'; } from '../lib';
import { validateScheduleLimit } from '../../application/rule/methods/get_schedule_frequency';
type ShouldIncrementRevision = (params?: RuleTypeParams) => boolean; type ShouldIncrementRevision = (params?: RuleTypeParams) => boolean;
@ -88,6 +89,21 @@ async function updateWithOCC<Params extends RuleTypeParams>(
alertSavedObject = await context.unsecuredSavedObjectsClient.get<RawRule>('alert', id); alertSavedObject = await context.unsecuredSavedObjectsClient.get<RawRule>('alert', id);
} }
const {
attributes: { enabled, schedule },
} = alertSavedObject;
try {
if (enabled && schedule.interval !== data.schedule.interval) {
await validateScheduleLimit({
context,
prevInterval: alertSavedObject.attributes.schedule?.interval,
updatedInterval: data.schedule.interval,
});
}
} catch (error) {
throw Boom.badRequest(`Error validating update data - ${error.message}`);
}
try { try {
await context.authorization.ensureAuthorized({ await context.authorization.ensureAuthorized({
ruleTypeId: alertSavedObject.attributes.alertTypeId, ruleTypeId: alertSavedObject.attributes.alertTypeId,

View file

@ -57,6 +57,7 @@ import { runSoon } from './methods/run_soon';
import { listRuleTypes } from './methods/list_rule_types'; import { listRuleTypes } from './methods/list_rule_types';
import { getAlertFromRaw, GetAlertFromRawParams } from './lib/get_alert_from_raw'; import { getAlertFromRaw, GetAlertFromRawParams } from './lib/get_alert_from_raw';
import { getTags, GetTagsParams } from './methods/get_tags'; import { getTags, GetTagsParams } from './methods/get_tags';
import { getScheduleFrequency } from '../application/rule/methods/get_schedule_frequency/get_schedule_frequency';
export type ConstructorOptions = Omit< export type ConstructorOptions = Omit<
RulesClientContext, RulesClientContext,
@ -179,6 +180,8 @@ export class RulesClient {
public getTags = (params: GetTagsParams) => getTags(this.context, params); public getTags = (params: GetTagsParams) => getTags(this.context, params);
public getScheduleFrequency = () => getScheduleFrequency(this.context);
public getAlertFromRaw = (params: GetAlertFromRawParams) => public getAlertFromRaw = (params: GetAlertFromRawParams) =>
getAlertFromRaw( getAlertFromRaw(
this.context, this.context,

View file

@ -6,7 +6,11 @@
*/ */
import { RulesClient, ConstructorOptions } from '../rules_client'; import { RulesClient, ConstructorOptions } from '../rules_client';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
@ -32,12 +36,14 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertingAuthorizationMock.create(); const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditLoggerMock.create(); const auditLogger = auditLoggerMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v7.10.0'; const kibanaVersion = 'v7.10.0';
const rulesClientParams: jest.Mocked<ConstructorOptions> = { const rulesClientParams: jest.Mocked<ConstructorOptions> = {
taskManager, taskManager,
ruleTypeRegistry, ruleTypeRegistry,
unsecuredSavedObjectsClient, unsecuredSavedObjectsClient,
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
authorization: authorization as unknown as AlertingAuthorization, authorization: authorization as unknown as AlertingAuthorization,
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
@ -46,6 +52,7 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: jest.fn(), createAPIKey: jest.fn(),
logger: loggingSystemMock.create().get(), logger: loggingSystemMock.create().get(),
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),

View file

@ -6,7 +6,7 @@
*/ */
import { RulesClient, ConstructorOptions } from '../rules_client'; import { RulesClient, ConstructorOptions } from '../rules_client';
import { savedObjectsClientMock } from '@kbn/core/server/mocks'; import { savedObjectsClientMock, savedObjectsRepositoryMock } from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
@ -53,6 +53,7 @@ const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditLoggerMock.create(); const auditLogger = auditLoggerMock.create();
const logger = loggerMock.create(); const logger = loggerMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v8.2.0'; const kibanaVersion = 'v8.2.0';
const createAPIKeyMock = jest.fn(); const createAPIKeyMock = jest.fn();
@ -67,11 +68,13 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: createAPIKeyMock, createAPIKey: createAPIKeyMock,
logger, logger,
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),
kibanaVersion, kibanaVersion,
auditLogger, auditLogger,
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
isAuthenticationTypeAPIKey: jest.fn(), isAuthenticationTypeAPIKey: jest.fn(),
getAuthenticationAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(),

View file

@ -6,7 +6,7 @@
*/ */
import { AlertConsumers } from '@kbn/rule-data-utils'; import { AlertConsumers } from '@kbn/rule-data-utils';
import { RulesClient, ConstructorOptions } from '../rules_client'; import { RulesClient, ConstructorOptions } from '../rules_client';
import { savedObjectsClientMock } from '@kbn/core/server/mocks'; import { savedObjectsClientMock, savedObjectsRepositoryMock } from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import type { SavedObject } from '@kbn/core-saved-objects-server'; import type { SavedObject } from '@kbn/core-saved-objects-server';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
@ -61,6 +61,7 @@ const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditLoggerMock.create(); const auditLogger = auditLoggerMock.create();
const logger = loggerMock.create(); const logger = loggerMock.create();
const eventLogger = eventLoggerMock.create(); const eventLogger = eventLoggerMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v8.2.0'; const kibanaVersion = 'v8.2.0';
const createAPIKeyMock = jest.fn(); const createAPIKeyMock = jest.fn();
@ -75,12 +76,14 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: createAPIKeyMock, createAPIKey: createAPIKeyMock,
logger, logger,
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),
kibanaVersion, kibanaVersion,
auditLogger, auditLogger,
eventLogger, eventLogger,
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
isAuthenticationTypeAPIKey: jest.fn(), isAuthenticationTypeAPIKey: jest.fn(),
getAuthenticationAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(),

View file

@ -6,7 +6,7 @@
*/ */
import { AlertConsumers } from '@kbn/rule-data-utils'; import { AlertConsumers } from '@kbn/rule-data-utils';
import { RulesClient, ConstructorOptions } from '../rules_client'; import { RulesClient, ConstructorOptions } from '../rules_client';
import { savedObjectsClientMock } from '@kbn/core/server/mocks'; import { savedObjectsClientMock, savedObjectsRepositoryMock } from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
@ -45,6 +45,10 @@ jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation
bulkMarkApiKeysForInvalidation: jest.fn(), bulkMarkApiKeysForInvalidation: jest.fn(),
})); }));
jest.mock('../../application/rule/methods/get_schedule_frequency', () => ({
validateScheduleLimit: jest.fn(),
}));
const taskManager = taskManagerMock.createStart(); const taskManager = taskManagerMock.createStart();
const ruleTypeRegistry = ruleTypeRegistryMock.create(); const ruleTypeRegistry = ruleTypeRegistryMock.create();
const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
@ -53,6 +57,7 @@ const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditLoggerMock.create(); const auditLogger = auditLoggerMock.create();
const logger = loggerMock.create(); const logger = loggerMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v8.2.0'; const kibanaVersion = 'v8.2.0';
const createAPIKeyMock = jest.fn(); const createAPIKeyMock = jest.fn();
@ -67,11 +72,13 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: createAPIKeyMock, createAPIKey: createAPIKeyMock,
logger, logger,
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),
kibanaVersion, kibanaVersion,
auditLogger, auditLogger,
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
isAuthenticationTypeAPIKey: jest.fn(), isAuthenticationTypeAPIKey: jest.fn(),
getAuthenticationAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(),

View file

@ -8,7 +8,11 @@
import moment from 'moment'; import moment from 'moment';
import sinon from 'sinon'; import sinon from 'sinon';
import { RulesClient, ConstructorOptions } from '../rules_client'; import { RulesClient, ConstructorOptions } from '../rules_client';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
@ -40,6 +44,7 @@ const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditLoggerMock.create(); const auditLogger = auditLoggerMock.create();
const eventLogger = eventLoggerMock.create(); const eventLogger = eventLoggerMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v7.10.0'; const kibanaVersion = 'v7.10.0';
const rulesClientParams: jest.Mocked<ConstructorOptions> = { const rulesClientParams: jest.Mocked<ConstructorOptions> = {
@ -50,10 +55,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
spaceId: 'default', spaceId: 'default',
namespace: 'default', namespace: 'default',
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: jest.fn(), createAPIKey: jest.fn(),
logger: loggingSystemMock.create().get(), logger: loggingSystemMock.create().get(),
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),

View file

@ -8,7 +8,11 @@
import { AlertConsumers } from '@kbn/rule-data-utils'; import { AlertConsumers } from '@kbn/rule-data-utils';
import { RulesClient, ConstructorOptions } from '../rules_client'; import { RulesClient, ConstructorOptions } from '../rules_client';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
@ -43,12 +47,14 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertingAuthorizationMock.create(); const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditLoggerMock.create(); const auditLogger = auditLoggerMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v7.10.0'; const kibanaVersion = 'v7.10.0';
const rulesClientParams: jest.Mocked<ConstructorOptions> = { const rulesClientParams: jest.Mocked<ConstructorOptions> = {
taskManager, taskManager,
ruleTypeRegistry, ruleTypeRegistry,
unsecuredSavedObjectsClient, unsecuredSavedObjectsClient,
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
authorization: authorization as unknown as AlertingAuthorization, authorization: authorization as unknown as AlertingAuthorization,
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
@ -57,6 +63,7 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: jest.fn(), createAPIKey: jest.fn(),
logger: loggingSystemMock.create().get(), logger: loggingSystemMock.create().get(),
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),

View file

@ -7,7 +7,11 @@
import { AlertConsumers } from '@kbn/rule-data-utils'; import { AlertConsumers } from '@kbn/rule-data-utils';
import { RulesClient, ConstructorOptions } from '../rules_client'; import { RulesClient, ConstructorOptions } from '../rules_client';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
@ -44,6 +48,7 @@ const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditLoggerMock.create(); const auditLogger = auditLoggerMock.create();
const eventLogger = eventLoggerMock.create(); const eventLogger = eventLoggerMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v7.10.0'; const kibanaVersion = 'v7.10.0';
const rulesClientParams: jest.Mocked<ConstructorOptions> = { const rulesClientParams: jest.Mocked<ConstructorOptions> = {
@ -54,10 +59,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
spaceId: 'default', spaceId: 'default',
namespace: 'default', namespace: 'default',
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: jest.fn(), createAPIKey: jest.fn(),
logger: loggingSystemMock.create().get(), logger: loggingSystemMock.create().get(),
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),

View file

@ -7,7 +7,11 @@
import { AlertConsumers } from '@kbn/rule-data-utils'; import { AlertConsumers } from '@kbn/rule-data-utils';
import { RulesClient, ConstructorOptions } from '../rules_client'; import { RulesClient, ConstructorOptions } from '../rules_client';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
@ -31,6 +35,10 @@ jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation
bulkMarkApiKeysForInvalidation: jest.fn(), bulkMarkApiKeysForInvalidation: jest.fn(),
})); }));
jest.mock('../../application/rule/methods/get_schedule_frequency', () => ({
validateScheduleLimit: jest.fn(),
}));
const taskManager = taskManagerMock.createStart(); const taskManager = taskManagerMock.createStart();
const ruleTypeRegistry = ruleTypeRegistryMock.create(); const ruleTypeRegistry = ruleTypeRegistryMock.create();
const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
@ -38,6 +46,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertingAuthorizationMock.create(); const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditLoggerMock.create(); const auditLogger = auditLoggerMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v7.10.0'; const kibanaVersion = 'v7.10.0';
const rulesClientParams: jest.Mocked<ConstructorOptions> = { const rulesClientParams: jest.Mocked<ConstructorOptions> = {
@ -48,10 +57,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
spaceId: 'default', spaceId: 'default',
namespace: 'default', namespace: 'default',
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: jest.fn(), createAPIKey: jest.fn(),
logger: loggingSystemMock.create().get(), logger: loggingSystemMock.create().get(),
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),

View file

@ -6,7 +6,11 @@
*/ */
import { RulesClient, ConstructorOptions } from '../rules_client'; import { RulesClient, ConstructorOptions } from '../rules_client';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
@ -36,6 +40,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertingAuthorizationMock.create(); const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditLoggerMock.create(); const auditLogger = auditLoggerMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v7.10.0'; const kibanaVersion = 'v7.10.0';
const rulesClientParams: jest.Mocked<ConstructorOptions> = { const rulesClientParams: jest.Mocked<ConstructorOptions> = {
@ -46,10 +51,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
spaceId: 'default', spaceId: 'default',
namespace: 'default', namespace: 'default',
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: jest.fn(), createAPIKey: jest.fn(),
logger: loggingSystemMock.create().get(), logger: loggingSystemMock.create().get(),
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),

View file

@ -7,7 +7,11 @@
import { AlertConsumers } from '@kbn/rule-data-utils'; import { AlertConsumers } from '@kbn/rule-data-utils';
import { RulesClient, ConstructorOptions } from '../rules_client'; import { RulesClient, ConstructorOptions } from '../rules_client';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
@ -33,6 +37,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertingAuthorizationMock.create(); const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditLoggerMock.create(); const auditLogger = auditLoggerMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v7.10.0'; const kibanaVersion = 'v7.10.0';
const rulesClientParams: jest.Mocked<ConstructorOptions> = { const rulesClientParams: jest.Mocked<ConstructorOptions> = {
@ -43,10 +48,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
spaceId: 'default', spaceId: 'default',
namespace: 'default', namespace: 'default',
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: jest.fn(), createAPIKey: jest.fn(),
logger: loggingSystemMock.create().get(), logger: loggingSystemMock.create().get(),
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),

View file

@ -7,7 +7,11 @@
import { RulesClient, ConstructorOptions } from '../rules_client'; import { RulesClient, ConstructorOptions } from '../rules_client';
import { GetActionErrorLogByIdParams } from '../methods/get_action_error_log'; import { GetActionErrorLogByIdParams } from '../methods/get_action_error_log';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { fromKueryExpression } from '@kbn/es-query'; import { fromKueryExpression } from '@kbn/es-query';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
@ -31,6 +35,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertingAuthorizationMock.create(); const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditLoggerMock.create(); const auditLogger = auditLoggerMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v7.10.0'; const kibanaVersion = 'v7.10.0';
const rulesClientParams: jest.Mocked<ConstructorOptions> = { const rulesClientParams: jest.Mocked<ConstructorOptions> = {
@ -41,10 +46,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
spaceId: 'default', spaceId: 'default',
namespace: 'default', namespace: 'default',
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: jest.fn(), createAPIKey: jest.fn(),
logger: loggingSystemMock.create().get(), logger: loggingSystemMock.create().get(),
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),

View file

@ -6,7 +6,11 @@
*/ */
import { RulesClient, ConstructorOptions } from '../rules_client'; import { RulesClient, ConstructorOptions } from '../rules_client';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
@ -24,6 +28,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertingAuthorizationMock.create(); const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v7.10.0'; const kibanaVersion = 'v7.10.0';
const rulesClientParams: jest.Mocked<ConstructorOptions> = { const rulesClientParams: jest.Mocked<ConstructorOptions> = {
@ -34,10 +39,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
spaceId: 'default', spaceId: 'default',
namespace: 'default', namespace: 'default',
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: jest.fn(), createAPIKey: jest.fn(),
logger: loggingSystemMock.create().get(), logger: loggingSystemMock.create().get(),
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),

View file

@ -7,7 +7,11 @@
import { omit, mean } from 'lodash'; import { omit, mean } from 'lodash';
import { RulesClient, ConstructorOptions } from '../rules_client'; import { RulesClient, ConstructorOptions } from '../rules_client';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
@ -30,6 +34,7 @@ const eventLogClient = eventLogClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertingAuthorizationMock.create(); const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v7.10.0'; const kibanaVersion = 'v7.10.0';
const rulesClientParams: jest.Mocked<ConstructorOptions> = { const rulesClientParams: jest.Mocked<ConstructorOptions> = {
@ -40,10 +45,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
spaceId: 'default', spaceId: 'default',
namespace: 'default', namespace: 'default',
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: jest.fn(), createAPIKey: jest.fn(),
logger: loggingSystemMock.create().get(), logger: loggingSystemMock.create().get(),
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),

View file

@ -7,7 +7,11 @@
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { RulesClient, ConstructorOptions } from '../rules_client'; import { RulesClient, ConstructorOptions } from '../rules_client';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
@ -32,6 +36,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertingAuthorizationMock.create(); const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditLoggerMock.create(); const auditLogger = auditLoggerMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v7.10.0'; const kibanaVersion = 'v7.10.0';
const rulesClientParams: jest.Mocked<ConstructorOptions> = { const rulesClientParams: jest.Mocked<ConstructorOptions> = {
@ -42,10 +47,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
spaceId: 'default', spaceId: 'default',
namespace: 'default', namespace: 'default',
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: jest.fn(), createAPIKey: jest.fn(),
logger: loggingSystemMock.create().get(), logger: loggingSystemMock.create().get(),
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),

View file

@ -6,7 +6,11 @@
*/ */
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { RulesClient, ConstructorOptions } from '../rules_client'; import { RulesClient, ConstructorOptions } from '../rules_client';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
@ -27,12 +31,14 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertingAuthorizationMock.create(); const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditLoggerMock.create(); const auditLogger = auditLoggerMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v7.10.0'; const kibanaVersion = 'v7.10.0';
const rulesClientParams: jest.Mocked<ConstructorOptions> = { const rulesClientParams: jest.Mocked<ConstructorOptions> = {
taskManager, taskManager,
ruleTypeRegistry, ruleTypeRegistry,
unsecuredSavedObjectsClient, unsecuredSavedObjectsClient,
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
authorization: authorization as unknown as AlertingAuthorization, authorization: authorization as unknown as AlertingAuthorization,
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
@ -41,6 +47,7 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: jest.fn(), createAPIKey: jest.fn(),
logger: loggingSystemMock.create().get(), logger: loggingSystemMock.create().get(),
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),

View file

@ -6,7 +6,11 @@
*/ */
import { RulesClient, ConstructorOptions } from '../rules_client'; import { RulesClient, ConstructorOptions } from '../rules_client';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
@ -28,6 +32,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertingAuthorizationMock.create(); const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v7.10.0'; const kibanaVersion = 'v7.10.0';
const rulesClientParams: jest.Mocked<ConstructorOptions> = { const rulesClientParams: jest.Mocked<ConstructorOptions> = {
@ -38,10 +43,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
spaceId: 'default', spaceId: 'default',
namespace: 'default', namespace: 'default',
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: jest.fn(), createAPIKey: jest.fn(),
logger: loggingSystemMock.create().get(), logger: loggingSystemMock.create().get(),
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),

View file

@ -6,7 +6,11 @@
*/ */
import { RulesClient, ConstructorOptions } from '../rules_client'; import { RulesClient, ConstructorOptions } from '../rules_client';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
@ -24,6 +28,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertingAuthorizationMock.create(); const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditLoggerMock.create(); const auditLogger = auditLoggerMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v7.10.0'; const kibanaVersion = 'v7.10.0';
const rulesClientParams: jest.Mocked<ConstructorOptions> = { const rulesClientParams: jest.Mocked<ConstructorOptions> = {
@ -34,10 +39,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
spaceId: 'default', spaceId: 'default',
namespace: 'default', namespace: 'default',
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: jest.fn(), createAPIKey: jest.fn(),
logger: loggingSystemMock.create().get(), logger: loggingSystemMock.create().get(),
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),

View file

@ -6,7 +6,11 @@
*/ */
import { RulesClient, ConstructorOptions } from '../rules_client'; import { RulesClient, ConstructorOptions } from '../rules_client';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
@ -24,6 +28,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertingAuthorizationMock.create(); const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditLoggerMock.create(); const auditLogger = auditLoggerMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v7.10.0'; const kibanaVersion = 'v7.10.0';
const rulesClientParams: jest.Mocked<ConstructorOptions> = { const rulesClientParams: jest.Mocked<ConstructorOptions> = {
@ -34,10 +39,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
spaceId: 'default', spaceId: 'default',
namespace: 'default', namespace: 'default',
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: jest.fn(), createAPIKey: jest.fn(),
logger: loggingSystemMock.create().get(), logger: loggingSystemMock.create().get(),
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),

View file

@ -7,7 +7,11 @@
import { AlertConsumers } from '@kbn/rule-data-utils'; import { AlertConsumers } from '@kbn/rule-data-utils';
import { RulesClient, ConstructorOptions } from '../rules_client'; import { RulesClient, ConstructorOptions } from '../rules_client';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
@ -33,6 +37,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertingAuthorizationMock.create(); const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditLoggerMock.create(); const auditLogger = auditLoggerMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v7.10.0'; const kibanaVersion = 'v7.10.0';
const rulesClientParams: jest.Mocked<ConstructorOptions> = { const rulesClientParams: jest.Mocked<ConstructorOptions> = {
@ -43,10 +48,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
spaceId: 'default', spaceId: 'default',
namespace: 'default', namespace: 'default',
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: jest.fn(), createAPIKey: jest.fn(),
logger: loggingSystemMock.create().get(), logger: loggingSystemMock.create().get(),
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),

View file

@ -6,7 +6,11 @@
*/ */
import { RulesClient, ConstructorOptions } from '../rules_client'; import { RulesClient, ConstructorOptions } from '../rules_client';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
@ -25,6 +29,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertingAuthorizationMock.create(); const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditLoggerMock.create(); const auditLogger = auditLoggerMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v7.10.0'; const kibanaVersion = 'v7.10.0';
const rulesClientParams: jest.Mocked<ConstructorOptions> = { const rulesClientParams: jest.Mocked<ConstructorOptions> = {
@ -35,10 +40,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
spaceId: 'default', spaceId: 'default',
namespace: 'default', namespace: 'default',
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: jest.fn(), createAPIKey: jest.fn(),
logger: loggingSystemMock.create().get(), logger: loggingSystemMock.create().get(),
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),

View file

@ -6,7 +6,11 @@
*/ */
import { RulesClient, ConstructorOptions } from '../rules_client'; import { RulesClient, ConstructorOptions } from '../rules_client';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
@ -24,6 +28,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertingAuthorizationMock.create(); const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditLoggerMock.create(); const auditLogger = auditLoggerMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v7.10.0'; const kibanaVersion = 'v7.10.0';
const rulesClientParams: jest.Mocked<ConstructorOptions> = { const rulesClientParams: jest.Mocked<ConstructorOptions> = {
@ -34,10 +39,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
spaceId: 'default', spaceId: 'default',
namespace: 'default', namespace: 'default',
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: jest.fn(), createAPIKey: jest.fn(),
logger: loggingSystemMock.create().get(), logger: loggingSystemMock.create().get(),
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),

View file

@ -6,7 +6,11 @@
*/ */
import { RulesClient, ConstructorOptions } from '../rules_client'; import { RulesClient, ConstructorOptions } from '../rules_client';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
@ -24,6 +28,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertingAuthorizationMock.create(); const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditLoggerMock.create(); const auditLogger = auditLoggerMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v7.10.0'; const kibanaVersion = 'v7.10.0';
const rulesClientParams: jest.Mocked<ConstructorOptions> = { const rulesClientParams: jest.Mocked<ConstructorOptions> = {
@ -34,10 +39,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
spaceId: 'default', spaceId: 'default',
namespace: 'default', namespace: 'default',
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: jest.fn(), createAPIKey: jest.fn(),
logger: loggingSystemMock.create().get(), logger: loggingSystemMock.create().get(),
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),

View file

@ -9,7 +9,11 @@ import { v4 as uuidv4 } from 'uuid';
import { schema } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema';
import { AlertConsumers } from '@kbn/rule-data-utils'; import { AlertConsumers } from '@kbn/rule-data-utils';
import { RulesClient, ConstructorOptions } from '../rules_client'; import { RulesClient, ConstructorOptions } from '../rules_client';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
@ -50,6 +54,10 @@ jest.mock('uuid', () => {
return { v4: () => `${uuid++}` }; return { v4: () => `${uuid++}` };
}); });
jest.mock('../../application/rule/methods/get_schedule_frequency', () => ({
validateScheduleLimit: jest.fn(),
}));
const bulkMarkApiKeysForInvalidationMock = bulkMarkApiKeysForInvalidation as jest.Mock; const bulkMarkApiKeysForInvalidationMock = bulkMarkApiKeysForInvalidation as jest.Mock;
const taskManager = taskManagerMock.createStart(); const taskManager = taskManagerMock.createStart();
const ruleTypeRegistry = ruleTypeRegistryMock.create(); const ruleTypeRegistry = ruleTypeRegistryMock.create();
@ -58,6 +66,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertingAuthorizationMock.create(); const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditLoggerMock.create(); const auditLogger = auditLoggerMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v7.10.0'; const kibanaVersion = 'v7.10.0';
const rulesClientParams: jest.Mocked<ConstructorOptions> = { const rulesClientParams: jest.Mocked<ConstructorOptions> = {
@ -71,11 +80,13 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: jest.fn(), createAPIKey: jest.fn(),
logger: loggingSystemMock.create().get(), logger: loggingSystemMock.create().get(),
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),
kibanaVersion, kibanaVersion,
auditLogger, auditLogger,
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
isAuthenticationTypeAPIKey: jest.fn(), isAuthenticationTypeAPIKey: jest.fn(),
getAuthenticationAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(),

View file

@ -6,7 +6,11 @@
*/ */
import { RulesClient, ConstructorOptions } from '../rules_client'; import { RulesClient, ConstructorOptions } from '../rules_client';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
@ -30,6 +34,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertingAuthorizationMock.create(); const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditLoggerMock.create(); const auditLogger = auditLoggerMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v7.10.0'; const kibanaVersion = 'v7.10.0';
const rulesClientParams: jest.Mocked<ConstructorOptions> = { const rulesClientParams: jest.Mocked<ConstructorOptions> = {
@ -40,10 +45,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
spaceId: 'default', spaceId: 'default',
namespace: 'default', namespace: 'default',
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: jest.fn(), createAPIKey: jest.fn(),
logger: loggingSystemMock.create().get(), logger: loggingSystemMock.create().get(),
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),

View file

@ -6,7 +6,12 @@
*/ */
import { KueryNode } from '@kbn/es-query'; import { KueryNode } from '@kbn/es-query';
import { Logger, SavedObjectsClientContract, PluginInitializerContext } from '@kbn/core/server'; import {
Logger,
SavedObjectsClientContract,
PluginInitializerContext,
ISavedObjectsRepository,
} from '@kbn/core/server';
import { ActionsClient, ActionsAuthorization } from '@kbn/actions-plugin/server'; import { ActionsClient, ActionsAuthorization } from '@kbn/actions-plugin/server';
import { import {
GrantAPIKeyResult as SecurityPluginGrantAPIKeyResult, GrantAPIKeyResult as SecurityPluginGrantAPIKeyResult,
@ -55,11 +60,13 @@ export interface RulesClientContext {
readonly authorization: AlertingAuthorization; readonly authorization: AlertingAuthorization;
readonly ruleTypeRegistry: RuleTypeRegistry; readonly ruleTypeRegistry: RuleTypeRegistry;
readonly minimumScheduleInterval: AlertingRulesConfig['minimumScheduleInterval']; readonly minimumScheduleInterval: AlertingRulesConfig['minimumScheduleInterval'];
readonly maxScheduledPerMinute: AlertingRulesConfig['maxScheduledPerMinute'];
readonly minimumScheduleIntervalInMs: number; readonly minimumScheduleIntervalInMs: number;
readonly createAPIKey: (name: string) => Promise<CreateAPIKeyResult>; readonly createAPIKey: (name: string) => Promise<CreateAPIKeyResult>;
readonly getActionsClient: () => Promise<ActionsClient>; readonly getActionsClient: () => Promise<ActionsClient>;
readonly actionsAuthorization: ActionsAuthorization; readonly actionsAuthorization: ActionsAuthorization;
readonly getEventLogClient: () => Promise<IEventLogClient>; readonly getEventLogClient: () => Promise<IEventLogClient>;
readonly internalSavedObjectsRepository: ISavedObjectsRepository;
readonly encryptedSavedObjectsClient: EncryptedSavedObjectsClient; readonly encryptedSavedObjectsClient: EncryptedSavedObjectsClient;
readonly kibanaVersion: PluginInitializerContext['env']['packageInfo']['version']; readonly kibanaVersion: PluginInitializerContext['env']['packageInfo']['version'];
readonly auditLogger?: AuditLogger; readonly auditLogger?: AuditLogger;

View file

@ -8,7 +8,11 @@
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { RulesClient, ConstructorOptions } from './rules_client'; import { RulesClient, ConstructorOptions } from './rules_client';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from './rule_type_registry.mock'; import { ruleTypeRegistryMock } from './rule_type_registry.mock';
import { alertingAuthorizationMock } from './authorization/alerting_authorization.mock'; import { alertingAuthorizationMock } from './authorization/alerting_authorization.mock';
@ -21,6 +25,10 @@ import { RetryForConflictsAttempts } from './lib/retry_if_conflicts';
import { TaskStatus } from '@kbn/task-manager-plugin/server/task'; import { TaskStatus } from '@kbn/task-manager-plugin/server/task';
import { RecoveredActionGroup } from '../common'; import { RecoveredActionGroup } from '../common';
jest.mock('./application/rule/methods/get_schedule_frequency', () => ({
validateScheduleLimit: jest.fn(),
}));
let rulesClient: RulesClient; let rulesClient: RulesClient;
const MockAlertId = 'alert-id'; const MockAlertId = 'alert-id';
@ -34,6 +42,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertingAuthorizationMock.create(); const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v7.10.0'; const kibanaVersion = 'v7.10.0';
const logger = loggingSystemMock.create().get(); const logger = loggingSystemMock.create().get();
@ -48,10 +57,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
getUserName: jest.fn(), getUserName: jest.fn(),
createAPIKey: jest.fn(), createAPIKey: jest.fn(),
logger, logger,
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjects, encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(), getActionsClient: jest.fn(),
getEventLogClient: jest.fn(), getEventLogClient: jest.fn(),
kibanaVersion, kibanaVersion,
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
isAuthenticationTypeAPIKey: jest.fn(), isAuthenticationTypeAPIKey: jest.fn(),
getAuthenticationAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(),

View file

@ -12,6 +12,7 @@ import {
savedObjectsClientMock, savedObjectsClientMock,
savedObjectsServiceMock, savedObjectsServiceMock,
loggingSystemMock, loggingSystemMock,
savedObjectsRepositoryMock,
} from '@kbn/core/server/mocks'; } from '@kbn/core/server/mocks';
import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks';
import { AuthenticatedUser } from '@kbn/security-plugin/common/model'; import { AuthenticatedUser } from '@kbn/security-plugin/common/model';
@ -37,6 +38,7 @@ const securityPluginStart = securityMock.createStart();
const alertingAuthorization = alertingAuthorizationMock.create(); const alertingAuthorization = alertingAuthorizationMock.create();
const alertingAuthorizationClientFactory = alertingAuthorizationClientFactoryMock.createFactory(); const alertingAuthorizationClientFactory = alertingAuthorizationClientFactoryMock.createFactory();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const rulesClientFactoryParams: jest.Mocked<RulesClientFactoryOpts> = { const rulesClientFactoryParams: jest.Mocked<RulesClientFactoryOpts> = {
logger: loggingSystemMock.create().get(), logger: loggingSystemMock.create().get(),
@ -44,7 +46,9 @@ const rulesClientFactoryParams: jest.Mocked<RulesClientFactoryOpts> = {
ruleTypeRegistry: ruleTypeRegistryMock.create(), ruleTypeRegistry: ruleTypeRegistryMock.create(),
getSpaceId: jest.fn(), getSpaceId: jest.fn(),
spaceIdToNamespace: jest.fn(), spaceIdToNamespace: jest.fn(),
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
internalSavedObjectsRepository,
encryptedSavedObjectsClient: encryptedSavedObjectsMock.createClient(), encryptedSavedObjectsClient: encryptedSavedObjectsMock.createClient(),
actions: actionsMock.createStart(), actions: actionsMock.createStart(),
eventLog: eventLogMock.createStart(), eventLog: eventLogMock.createStart(),
@ -101,8 +105,10 @@ test('creates a rules client with proper constructor arguments when security is
getActionsClient: expect.any(Function), getActionsClient: expect.any(Function),
getEventLogClient: expect.any(Function), getEventLogClient: expect.any(Function),
createAPIKey: expect.any(Function), createAPIKey: expect.any(Function),
internalSavedObjectsRepository: rulesClientFactoryParams.internalSavedObjectsRepository,
encryptedSavedObjectsClient: rulesClientFactoryParams.encryptedSavedObjectsClient, encryptedSavedObjectsClient: rulesClientFactoryParams.encryptedSavedObjectsClient,
kibanaVersion: '7.10.0', kibanaVersion: '7.10.0',
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
isAuthenticationTypeAPIKey: expect.any(Function), isAuthenticationTypeAPIKey: expect.any(Function),
getAuthenticationAPIKey: expect.any(Function), getAuthenticationAPIKey: expect.any(Function),
@ -139,10 +145,12 @@ test('creates a rules client with proper constructor arguments', async () => {
namespace: 'default', namespace: 'default',
getUserName: expect.any(Function), getUserName: expect.any(Function),
createAPIKey: expect.any(Function), createAPIKey: expect.any(Function),
internalSavedObjectsRepository: rulesClientFactoryParams.internalSavedObjectsRepository,
encryptedSavedObjectsClient: rulesClientFactoryParams.encryptedSavedObjectsClient, encryptedSavedObjectsClient: rulesClientFactoryParams.encryptedSavedObjectsClient,
getActionsClient: expect.any(Function), getActionsClient: expect.any(Function),
getEventLogClient: expect.any(Function), getEventLogClient: expect.any(Function),
kibanaVersion: '7.10.0', kibanaVersion: '7.10.0',
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
isAuthenticationTypeAPIKey: expect.any(Function), isAuthenticationTypeAPIKey: expect.any(Function),
getAuthenticationAPIKey: expect.any(Function), getAuthenticationAPIKey: expect.any(Function),

View file

@ -10,6 +10,7 @@ import {
Logger, Logger,
SavedObjectsServiceStart, SavedObjectsServiceStart,
PluginInitializerContext, PluginInitializerContext,
ISavedObjectsRepository,
} from '@kbn/core/server'; } from '@kbn/core/server';
import { PluginStartContract as ActionsPluginStartContract } from '@kbn/actions-plugin/server'; import { PluginStartContract as ActionsPluginStartContract } from '@kbn/actions-plugin/server';
import { import {
@ -34,12 +35,14 @@ export interface RulesClientFactoryOpts {
getSpaceId: (request: KibanaRequest) => string; getSpaceId: (request: KibanaRequest) => string;
spaceIdToNamespace: SpaceIdToNamespaceFunction; spaceIdToNamespace: SpaceIdToNamespaceFunction;
encryptedSavedObjectsClient: EncryptedSavedObjectsClient; encryptedSavedObjectsClient: EncryptedSavedObjectsClient;
internalSavedObjectsRepository: ISavedObjectsRepository;
actions: ActionsPluginStartContract; actions: ActionsPluginStartContract;
eventLog: IEventLogClientService; eventLog: IEventLogClientService;
kibanaVersion: PluginInitializerContext['env']['packageInfo']['version']; kibanaVersion: PluginInitializerContext['env']['packageInfo']['version'];
authorization: AlertingAuthorizationClientFactory; authorization: AlertingAuthorizationClientFactory;
eventLogger?: IEventLogger; eventLogger?: IEventLogger;
minimumScheduleInterval: AlertingRulesConfig['minimumScheduleInterval']; minimumScheduleInterval: AlertingRulesConfig['minimumScheduleInterval'];
maxScheduledPerMinute: AlertingRulesConfig['maxScheduledPerMinute'];
} }
export class RulesClientFactory { export class RulesClientFactory {
@ -52,12 +55,14 @@ export class RulesClientFactory {
private getSpaceId!: (request: KibanaRequest) => string; private getSpaceId!: (request: KibanaRequest) => string;
private spaceIdToNamespace!: SpaceIdToNamespaceFunction; private spaceIdToNamespace!: SpaceIdToNamespaceFunction;
private encryptedSavedObjectsClient!: EncryptedSavedObjectsClient; private encryptedSavedObjectsClient!: EncryptedSavedObjectsClient;
private internalSavedObjectsRepository!: ISavedObjectsRepository;
private actions!: ActionsPluginStartContract; private actions!: ActionsPluginStartContract;
private eventLog!: IEventLogClientService; private eventLog!: IEventLogClientService;
private kibanaVersion!: PluginInitializerContext['env']['packageInfo']['version']; private kibanaVersion!: PluginInitializerContext['env']['packageInfo']['version'];
private authorization!: AlertingAuthorizationClientFactory; private authorization!: AlertingAuthorizationClientFactory;
private eventLogger?: IEventLogger; private eventLogger?: IEventLogger;
private minimumScheduleInterval!: AlertingRulesConfig['minimumScheduleInterval']; private minimumScheduleInterval!: AlertingRulesConfig['minimumScheduleInterval'];
private maxScheduledPerMinute!: AlertingRulesConfig['maxScheduledPerMinute'];
public initialize(options: RulesClientFactoryOpts) { public initialize(options: RulesClientFactoryOpts) {
if (this.isInitialized) { if (this.isInitialized) {
@ -72,12 +77,14 @@ export class RulesClientFactory {
this.securityPluginStart = options.securityPluginStart; this.securityPluginStart = options.securityPluginStart;
this.spaceIdToNamespace = options.spaceIdToNamespace; this.spaceIdToNamespace = options.spaceIdToNamespace;
this.encryptedSavedObjectsClient = options.encryptedSavedObjectsClient; this.encryptedSavedObjectsClient = options.encryptedSavedObjectsClient;
this.internalSavedObjectsRepository = options.internalSavedObjectsRepository;
this.actions = options.actions; this.actions = options.actions;
this.eventLog = options.eventLog; this.eventLog = options.eventLog;
this.kibanaVersion = options.kibanaVersion; this.kibanaVersion = options.kibanaVersion;
this.authorization = options.authorization; this.authorization = options.authorization;
this.eventLogger = options.eventLogger; this.eventLogger = options.eventLogger;
this.minimumScheduleInterval = options.minimumScheduleInterval; this.minimumScheduleInterval = options.minimumScheduleInterval;
this.maxScheduledPerMinute = options.maxScheduledPerMinute;
} }
public create(request: KibanaRequest, savedObjects: SavedObjectsServiceStart): RulesClient { public create(request: KibanaRequest, savedObjects: SavedObjectsServiceStart): RulesClient {
@ -95,6 +102,7 @@ export class RulesClientFactory {
taskManager: this.taskManager, taskManager: this.taskManager,
ruleTypeRegistry: this.ruleTypeRegistry, ruleTypeRegistry: this.ruleTypeRegistry,
minimumScheduleInterval: this.minimumScheduleInterval, minimumScheduleInterval: this.minimumScheduleInterval,
maxScheduledPerMinute: this.maxScheduledPerMinute,
unsecuredSavedObjectsClient: savedObjects.getScopedClient(request, { unsecuredSavedObjectsClient: savedObjects.getScopedClient(request, {
excludedExtensions: [SECURITY_EXTENSION_ID], excludedExtensions: [SECURITY_EXTENSION_ID],
includedHiddenTypes: ['alert', 'api_key_pending_invalidation'], includedHiddenTypes: ['alert', 'api_key_pending_invalidation'],
@ -102,6 +110,7 @@ export class RulesClientFactory {
authorization: this.authorization.create(request), authorization: this.authorization.create(request),
actionsAuthorization: actions.getActionsAuthorizationWithRequest(request), actionsAuthorization: actions.getActionsAuthorizationWithRequest(request),
namespace: this.spaceIdToNamespace(spaceId), namespace: this.spaceIdToNamespace(spaceId),
internalSavedObjectsRepository: this.internalSavedObjectsRepository,
encryptedSavedObjectsClient: this.encryptedSavedObjectsClient, encryptedSavedObjectsClient: this.encryptedSavedObjectsClient,
auditLogger: securityPluginSetup?.audit.asScoped(request), auditLogger: securityPluginSetup?.audit.asScoped(request),
async getUserName() { async getUserName() {

View file

@ -60,6 +60,7 @@ export function generateAlertingConfig(): AlertingConfig {
maxEphemeralActionsPerAlert: 10, maxEphemeralActionsPerAlert: 10,
cancelAlertsOnRuleTimeout: true, cancelAlertsOnRuleTimeout: true,
rules: { rules: {
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
run: { run: {
actions: { actions: {

View file

@ -51,6 +51,7 @@ describe('createConfigRoute', () => {
baseRoute: `/internal/triggers_actions_ui`, baseRoute: `/internal/triggers_actions_ui`,
alertingConfig: () => ({ alertingConfig: () => ({
isUsingSecurity: true, isUsingSecurity: true,
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
}), }),
getRulesClientWithRequest: () => mockRulesClient, getRulesClientWithRequest: () => mockRulesClient,
@ -64,7 +65,11 @@ describe('createConfigRoute', () => {
expect(mockResponse.ok).toBeCalled(); expect(mockResponse.ok).toBeCalled();
expect(mockResponse.ok.mock.calls[0][0]).toEqual({ expect(mockResponse.ok.mock.calls[0][0]).toEqual({
body: { isUsingSecurity: true, minimumScheduleInterval: { value: '1m', enforce: false } }, body: {
isUsingSecurity: true,
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false },
},
}); });
}); });
@ -80,6 +85,7 @@ describe('createConfigRoute', () => {
baseRoute: `/internal/triggers_actions_ui`, baseRoute: `/internal/triggers_actions_ui`,
alertingConfig: () => ({ alertingConfig: () => ({
isUsingSecurity: true, isUsingSecurity: true,
maxScheduledPerMinute: 10000,
minimumScheduleInterval: { value: '1m', enforce: false }, minimumScheduleInterval: { value: '1m', enforce: false },
}), }),
getRulesClientWithRequest: () => mockRulesClient, getRulesClientWithRequest: () => mockRulesClient,

View file

@ -28,6 +28,7 @@ interface CreateTestConfigOptions {
reportName?: string; reportName?: string;
useDedicatedTaskRunner: boolean; useDedicatedTaskRunner: boolean;
enableFooterInEmail?: boolean; enableFooterInEmail?: boolean;
maxScheduledPerMinute?: number;
} }
// test.not-enabled is specifically not enabled // test.not-enabled is specifically not enabled
@ -82,6 +83,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
reportName = undefined, reportName = undefined,
useDedicatedTaskRunner, useDedicatedTaskRunner,
enableFooterInEmail = true, enableFooterInEmail = true,
maxScheduledPerMinute,
} = options; } = options;
return async ({ readConfigFile }: FtrConfigProviderContext) => { return async ({ readConfigFile }: FtrConfigProviderContext) => {
@ -151,6 +153,11 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
? [`--xpack.actions.email.domain_allowlist=${JSON.stringify(emailDomainsAllowed)}`] ? [`--xpack.actions.email.domain_allowlist=${JSON.stringify(emailDomainsAllowed)}`]
: []; : [];
const maxScheduledPerMinuteSettings =
typeof maxScheduledPerMinute === 'number'
? [`--xpack.alerting.rules.maxScheduledPerMinute=${maxScheduledPerMinute}`]
: [];
return { return {
testFiles: testFiles ? testFiles : [require.resolve(`../${name}/tests/`)], testFiles: testFiles ? testFiles : [require.resolve(`../${name}/tests/`)],
servers, servers,
@ -199,6 +206,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
...actionsProxyUrl, ...actionsProxyUrl,
...customHostSettings, ...customHostSettings,
...emailSettings, ...emailSettings,
...maxScheduledPerMinuteSettings,
'--xpack.eventLog.logEntries=true', '--xpack.eventLog.logEntries=true',
'--xpack.task_manager.ephemeral_tasks.enabled=false', '--xpack.task_manager.ephemeral_tasks.enabled=false',
`--xpack.task_manager.unsafe.exclude_task_types=${JSON.stringify([ `--xpack.task_manager.unsafe.exclude_task_types=${JSON.stringify([

View file

@ -0,0 +1,20 @@
/*
* 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 { createTestConfig } from '../../common/config';
// eslint-disable-next-line import/no-default-export
export default createTestConfig('security_and_spaces', {
disabledPlugins: [],
license: 'trial',
ssl: true,
enableActionsProxy: true,
publicBaseUrl: true,
testFiles: [require.resolve('./tests/alerting/schedule_circuit_breaker')],
useDedicatedTaskRunner: true,
maxScheduledPerMinute: 10,
});

View file

@ -0,0 +1,124 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../../common/ftr_provider_context';
import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../../common/lib';
// eslint-disable-next-line import/no-default-export
export default function bulkEditWithCircuitBreakerTests({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const objectRemover = new ObjectRemover(supertest);
describe('Bulk edit with circuit breaker', () => {
afterEach(async () => {
await objectRemover.removeAll();
});
it('should prevent rules from being bulk edited if max schedules have been reached', async () => {
const { body: createdRule1 } = await supertest
.post(`${getUrlPrefix('space1')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(getTestRuleData({ schedule: { interval: '20s' } }))
.expect(200);
objectRemover.add('space1', createdRule1.id, 'rule', 'alerting');
const { body: createdRule2 } = await supertest
.post(`${getUrlPrefix('space1')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(getTestRuleData({ schedule: { interval: '20s' } }))
.expect(200);
objectRemover.add('space1', createdRule2.id, 'rule', 'alerting');
const { body: createdRule3 } = await supertest
.post(`${getUrlPrefix('space1')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(getTestRuleData({ schedule: { interval: '20s' } }))
.expect(200);
objectRemover.add('space1', createdRule3.id, 'rule', 'alerting');
const payload = {
ids: [createdRule2.id, createdRule3.id],
operations: [
{
operation: 'set',
field: 'schedule',
value: {
interval: '10s',
},
},
],
};
const { body } = await supertest
.post(`${getUrlPrefix('space1')}/internal/alerting/rules/_bulk_edit`)
.set('kbn-xsrf', 'foo')
.send(payload)
.expect(200);
expect(body.errors.length).eql(2);
expect(body.errors[0].message).eql(
'Failed to bulk edit rule - Run limit reached: The rule has 12 runs per minute; there are only 1 runs per minute available.'
);
});
it('should allow disabled rules to go over the circuit breaker', async () => {
const { body: createdRule1 } = await supertest
.post(`${getUrlPrefix('space1')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(getTestRuleData({ schedule: { interval: '20s' } }))
.expect(200);
objectRemover.add('space1', createdRule1.id, 'rule', 'alerting');
const { body: createdRule2 } = await supertest
.post(`${getUrlPrefix('space1')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(
getTestRuleData({
enabled: false,
schedule: { interval: '20s' },
})
)
.expect(200);
objectRemover.add('space1', createdRule2.id, 'rule', 'alerting');
const { body: createdRule3 } = await supertest
.post(`${getUrlPrefix('space1')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(
getTestRuleData({
enabled: false,
schedule: { interval: '20s' },
})
)
.expect(200);
objectRemover.add('space1', createdRule3.id, 'rule', 'alerting');
const payload = {
ids: [createdRule2.id, createdRule3.id],
operations: [
{
operation: 'set',
field: 'schedule',
value: {
interval: '10s',
},
},
],
};
const { body } = await supertest
.post(`${getUrlPrefix('space1')}/internal/alerting/rules/_bulk_edit`)
.set('kbn-xsrf', 'foo')
.send(payload)
.expect(200);
expect(body.rules.length).eql(2);
expect(body.errors.length).eql(0);
});
});
}

View file

@ -0,0 +1,66 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../../common/ftr_provider_context';
import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../../common/lib';
// eslint-disable-next-line import/no-default-export
export default function bulkEnableWithCircuitBreakerTests({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const objectRemover = new ObjectRemover(supertest);
describe('Bulk enable with circuit breaker', () => {
afterEach(async () => {
await objectRemover.removeAll();
});
it('should prevent rules from being bulk enabled if max schedules have been reached', async () => {
const { body: createdRule1 } = await supertest
.post(`${getUrlPrefix('space1')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(getTestRuleData({ schedule: { interval: '10s' } }))
.expect(200);
objectRemover.add('space1', createdRule1.id, 'rule', 'alerting');
const { body: createdRule2 } = await supertest
.post(`${getUrlPrefix('space1')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(
getTestRuleData({
enabled: false,
schedule: { interval: '20s' },
})
)
.expect(200);
objectRemover.add('space1', createdRule2.id, 'rule', 'alerting');
const { body: createdRule3 } = await supertest
.post(`${getUrlPrefix('space1')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(
getTestRuleData({
enabled: false,
schedule: { interval: '10s' },
})
)
.expect(200);
objectRemover.add('space1', createdRule3.id, 'rule', 'alerting');
const { body } = await supertest
.patch(`${getUrlPrefix('space1')}/internal/alerting/rules/_bulk_enable`)
.set('kbn-xsrf', 'foo')
.send({ ids: [createdRule2.id, createdRule3.id] })
.expect(200);
expect(body.errors.length).eql(2);
expect(body.errors[0].message).eql(
'Error validating enable rule data - Run limit reached: The rule has 9 runs per minute; there are only 4 runs per minute available.'
);
});
});
}

View file

@ -0,0 +1,73 @@
/*
* 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 { FtrProviderContext } from '../../../../../common/ftr_provider_context';
import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../../common/lib';
// eslint-disable-next-line import/no-default-export
export default function createWithCircuitBreakerTests({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const objectRemover = new ObjectRemover(supertest);
describe('Create with circuit breaker', () => {
afterEach(async () => {
await objectRemover.removeAll();
});
it('should prevent rules from being created if max schedules have been reached', async () => {
const { body: createdRule } = await supertest
.post(`${getUrlPrefix('space1')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(getTestRuleData({ schedule: { interval: '10s' } }))
.expect(200);
objectRemover.add('space1', createdRule.id, 'rule', 'alerting');
const { body } = await supertest
.post(`${getUrlPrefix('space1')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(getTestRuleData({ schedule: { interval: '10s' } }))
.expect(400);
});
it('should prevent rules from being created across spaces', async () => {
const { body: createdRule } = await supertest
.post(`${getUrlPrefix('space1')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(getTestRuleData({ schedule: { interval: '10s' } }))
.expect(200);
objectRemover.add('space1', createdRule.id, 'rule', 'alerting');
const { body } = await supertest
.post(`${getUrlPrefix('space2')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(getTestRuleData({ schedule: { interval: '10s' } }))
.expect(400);
});
it('should allow disabled rules to go over the circuit breaker', async () => {
const { body: createdRule1 } = await supertest
.post(`${getUrlPrefix('space1')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(getTestRuleData({ schedule: { interval: '10s' } }))
.expect(200);
objectRemover.add('space1', createdRule1.id, 'rule', 'alerting');
const { body: createdRule2 } = await supertest
.post(`${getUrlPrefix('space1')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(
getTestRuleData({
enabled: false,
schedule: { interval: '10s' },
})
)
.expect(200);
objectRemover.add('space1', createdRule2.id, 'rule', 'alerting');
});
});
}

View file

@ -0,0 +1,52 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../../common/ftr_provider_context';
import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../../common/lib';
// eslint-disable-next-line import/no-default-export
export default function enableWithCircuitBreakerTests({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const objectRemover = new ObjectRemover(supertest);
describe('Enable with circuit breaker', () => {
afterEach(async () => {
await objectRemover.removeAll();
});
it('should prevent rules from being enabled if max schedules have been reached', async () => {
const { body: createdRule1 } = await supertest
.post(`${getUrlPrefix('space1')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(getTestRuleData({ schedule: { interval: '10s' } }))
.expect(200);
objectRemover.add('space1', createdRule1.id, 'rule', 'alerting');
const { body: createdRule2 } = await supertest
.post(`${getUrlPrefix('space1')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(
getTestRuleData({
enabled: false,
schedule: { interval: '5s' },
})
)
.expect(200);
objectRemover.add('space1', createdRule2.id, 'rule', 'alerting');
const { body } = await supertest
.post(`${getUrlPrefix('space1')}/api/alerting/rule/${createdRule2.id}/_enable`)
.set('kbn-xsrf', 'foo')
.expect(400);
expect(body.message).eql(
'Error validating enable rule data - Run limit reached: The rule has 12 runs per minute; there are only 4 runs per minute available.'
);
});
});
}

View file

@ -0,0 +1,83 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../../common/ftr_provider_context';
import { UserAtSpaceScenarios } from '../../../../scenarios';
import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../../common/lib';
// eslint-disable-next-line import/no-default-export
export default function getScheduleFrequencyTests({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const supertestWithoutAuth = getService('supertestWithoutAuth');
const objectRemover = new ObjectRemover(supertest);
describe('getScheduleFrequency', () => {
before(async () => {
const { body: createdRule1 } = await supertest
.post(`${getUrlPrefix('space1')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(getTestRuleData({ schedule: { interval: '30s' } }))
.expect(200);
objectRemover.add('space1', createdRule1.id, 'rule', 'alerting');
const { body: createdRule2 } = await supertest
.post(`${getUrlPrefix('space1')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(getTestRuleData({ schedule: { interval: '1m' } }))
.expect(200);
objectRemover.add('space1', createdRule2.id, 'rule', 'alerting');
const { body: createdRule3 } = await supertest
.post(`${getUrlPrefix('space2')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(getTestRuleData({ schedule: { interval: '2m' } }))
.expect(200);
objectRemover.add('space2', createdRule3.id, 'rule', 'alerting');
const { body: createdRule4 } = await supertest
.post(`${getUrlPrefix('space2')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(getTestRuleData({ schedule: { interval: '30s' } }))
.expect(200);
objectRemover.add('space2', createdRule4.id, 'rule', 'alerting');
});
after(async () => {
await objectRemover.removeAll();
});
for (const scenario of UserAtSpaceScenarios) {
const { user, space } = scenario;
describe(scenario.id, () => {
it('should get the total and remaining schedule frequency', async () => {
const { body } = await supertestWithoutAuth
.get(`${getUrlPrefix(space.id)}/internal/alerting/rules/_schedule_frequency`)
.set('kbn-xsrf', 'foo')
.send()
.auth(user.username, user.password);
switch (scenario.id) {
case 'no_kibana_privileges at space1':
case 'space_1_all at space2':
case 'global_read at space1':
case 'space_1_all_alerts_none_actions at space1':
case 'superuser at space1':
case 'space_1_all at space1':
case 'space_1_all_with_restricted_fixture at space1':
expect(body.total_scheduled_per_minute).eql(5.5);
expect(body.remaining_schedules_per_minute).eql(4.5);
break;
default:
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
}
});
});
}
});
}

View file

@ -0,0 +1,31 @@
/*
* 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 { FtrProviderContext } from '../../../../../common/ftr_provider_context';
import { setupSpacesAndUsers, tearDown } from '../../../../setup';
// eslint-disable-next-line import/no-default-export
export default function alertingTests({ loadTestFile, getService }: FtrProviderContext) {
describe('Alerts - Group 3 - schedule circuit breaker', () => {
describe('alerts', () => {
before(async () => {
await setupSpacesAndUsers(getService);
});
after(async () => {
await tearDown(getService);
});
loadTestFile(require.resolve('./get_schedule_frequency'));
loadTestFile(require.resolve('./create_with_circuit_breaker'));
loadTestFile(require.resolve('./update_with_circuit_breaker'));
loadTestFile(require.resolve('./enable_with_circuit_breaker'));
loadTestFile(require.resolve('./bulk_enable_with_circuit_breaker'));
loadTestFile(require.resolve('./bulk_edit_with_circuit_breaker'));
});
});
}

View file

@ -0,0 +1,99 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../../common/ftr_provider_context';
import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../../common/lib';
// eslint-disable-next-line import/no-default-export
export default function updateWithCircuitBreakerTests({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const objectRemover = new ObjectRemover(supertest);
describe('Update with circuit breaker', () => {
afterEach(async () => {
await objectRemover.removeAll();
});
it('should prevent rules from being updated if max schedules have been reached', async () => {
const { body: createdRule1 } = await supertest
.post(`${getUrlPrefix('space1')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(getTestRuleData({ schedule: { interval: '20s' } }))
.expect(200);
objectRemover.add('space1', createdRule1.id, 'rule', 'alerting');
const { body: createdRule2 } = await supertest
.post(`${getUrlPrefix('space1')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(getTestRuleData({ schedule: { interval: '20s' } }))
.expect(200);
objectRemover.add('space1', createdRule2.id, 'rule', 'alerting');
const updatedData = {
name: 'bcd',
tags: ['bar'],
params: {
foo: true,
},
schedule: { interval: '5s' },
actions: [],
throttle: '1m',
notify_when: 'onThrottleInterval',
};
const { body } = await supertest
.put(`${getUrlPrefix('space1')}/api/alerting/rule/${createdRule2.id}`)
.set('kbn-xsrf', 'foo')
.send(updatedData)
.expect(400);
expect(body.message).eql(
'Error validating update data - Run limit reached: The rule has 12 runs per minute; there are only 7 runs per minute available.'
);
});
it('should allow disabled rules to go over the circuit breaker', async () => {
const { body: createdRule1 } = await supertest
.post(`${getUrlPrefix('space1')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(getTestRuleData({ schedule: { interval: '20s' } }))
.expect(200);
objectRemover.add('space1', createdRule1.id, 'rule', 'alerting');
const { body: createdRule2 } = await supertest
.post(`${getUrlPrefix('space1')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(
getTestRuleData({
enabled: false,
schedule: { interval: '20s' },
})
)
.expect(200);
objectRemover.add('space1', createdRule2.id, 'rule', 'alerting');
const updatedData = {
name: 'bcd',
tags: ['bar'],
params: {
foo: true,
},
schedule: { interval: '5s' },
actions: [],
throttle: '1m',
notify_when: 'onThrottleInterval',
};
await supertest
.put(`${getUrlPrefix('space1')}/api/alerting/rule/${createdRule2.id}`)
.set('kbn-xsrf', 'foo')
.send(updatedData)
.expect(200);
});
});
}