mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[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:
parent
1a9bb19e57
commit
456f47f3ab
77 changed files with 1773 additions and 138 deletions
|
@ -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/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_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/spaces_only/tests/alerting/group1/config.ts
|
||||
- x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/config.ts
|
||||
|
|
|
@ -117,6 +117,7 @@ xpack.alerting.rules.run.ruleTypeOverrides:
|
|||
- id: siem.indicatorRule
|
||||
timeout: 1m
|
||||
xpack.alerting.rules.minimumScheduleInterval.enforce: true
|
||||
xpack.alerting.rules.maxScheduledPerMinute: 400
|
||||
xpack.actions.run.maxAttempts: 10
|
||||
|
||||
# Disables ESQL in advanced settings (hides it from the UI)
|
||||
|
|
|
@ -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
|
||||
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}::
|
||||
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`).
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
formatDuration,
|
||||
getDurationNumberInItsUnit,
|
||||
getDurationUnitValue,
|
||||
convertDurationToFrequency,
|
||||
} from './parse_duration';
|
||||
|
||||
test('parses seconds', () => {
|
||||
|
@ -180,3 +181,26 @@ test('getDurationUnitValue hours', () => {
|
|||
const result = getDurationUnitValue('100h');
|
||||
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"`
|
||||
);
|
||||
});
|
||||
|
|
|
@ -10,6 +10,8 @@ const MINUTES_REGEX = /^[1-9][0-9]*m$/;
|
|||
const HOURS_REGEX = /^[1-9][0-9]*h$/;
|
||||
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
|
||||
export function parseDuration(duration: string): number {
|
||||
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 {
|
||||
return parseInt(duration.replace(/[^0-9.]/g, ''), 10);
|
||||
}
|
||||
|
|
|
@ -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';
|
|
@ -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';
|
|
@ -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,
|
||||
});
|
|
@ -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';
|
|
@ -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>;
|
|
@ -10,7 +10,11 @@ import { omit } from 'lodash';
|
|||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
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 { ruleTypeRegistryMock } from '../../../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../../../authorization/alerting_authorization.mock';
|
||||
|
@ -56,7 +60,12 @@ jest.mock('uuid', () => {
|
|||
return { v4: () => `${uuid++}` };
|
||||
});
|
||||
|
||||
jest.mock('../get_schedule_frequency', () => ({
|
||||
validateScheduleLimit: jest.fn(),
|
||||
}));
|
||||
|
||||
const { isSnoozeActive } = jest.requireMock('../../../../lib/snooze/is_snooze_active');
|
||||
const { validateScheduleLimit } = jest.requireMock('../get_schedule_frequency');
|
||||
|
||||
const taskManager = taskManagerMock.createStart();
|
||||
const ruleTypeRegistry = ruleTypeRegistryMock.create();
|
||||
|
@ -65,6 +74,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
|
|||
const authorization = alertingAuthorizationMock.create();
|
||||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const auditLogger = auditLoggerMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v8.2.0';
|
||||
const createAPIKeyMock = jest.fn();
|
||||
|
@ -82,11 +92,13 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getUserName: jest.fn(),
|
||||
createAPIKey: createAPIKeyMock,
|
||||
logger: loggingSystemMock.create().get(),
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
auditLogger,
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
isAuthenticationTypeAPIKey: isAuthenticationTypeApiKeyMock,
|
||||
getAuthenticationAPIKey: getAuthenticationApiKeyMock,
|
||||
|
@ -2495,6 +2507,66 @@ describe('bulkEdit()', () => {
|
|||
`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', () => {
|
||||
|
|
|
@ -77,6 +77,11 @@ import {
|
|||
transformRuleDomainToRuleAttributes,
|
||||
transformRuleDomainToRule,
|
||||
} 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']);
|
||||
|
||||
|
@ -286,8 +291,16 @@ async function bulkEditRulesOcc<Params extends RuleParams>(
|
|||
const errors: BulkOperationError[] = [];
|
||||
const apiKeysMap: ApiKeysMap = new Map();
|
||||
const username = await context.getUserName();
|
||||
const prevInterval: string[] = [];
|
||||
|
||||
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(
|
||||
response.saved_objects,
|
||||
async (rule: SavedObjectsFindResult<RuleAttributes>) =>
|
||||
|
@ -308,9 +321,44 @@ async function bulkEditRulesOcc<Params extends RuleParams>(
|
|||
}
|
||||
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 } =
|
||||
rules.length > 0
|
||||
? await saveBulkUpdatedRules(context, rules, apiKeysMap)
|
||||
? await saveBulkUpdatedRules({
|
||||
context,
|
||||
rules,
|
||||
apiKeysMap,
|
||||
})
|
||||
: {
|
||||
result: { saved_objects: [] },
|
||||
apiKeysToInvalidate: [],
|
||||
|
@ -821,11 +869,15 @@ function updateAttributes(
|
|||
};
|
||||
}
|
||||
|
||||
async function saveBulkUpdatedRules(
|
||||
context: RulesClientContext,
|
||||
rules: Array<SavedObjectsBulkUpdateObject<RuleAttributes>>,
|
||||
apiKeysMap: ApiKeysMap
|
||||
) {
|
||||
async function saveBulkUpdatedRules({
|
||||
context,
|
||||
rules,
|
||||
apiKeysMap,
|
||||
}: {
|
||||
context: RulesClientContext;
|
||||
rules: Array<SavedObjectsBulkUpdateObject<RuleAttributes>>;
|
||||
apiKeysMap: ApiKeysMap;
|
||||
}) {
|
||||
const apiKeysToInvalidate: string[] = [];
|
||||
let result;
|
||||
try {
|
||||
|
|
|
@ -8,7 +8,11 @@
|
|||
import { schema } from '@kbn/config-schema';
|
||||
import { CreateRuleParams } from './create_rule';
|
||||
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 { ruleTypeRegistryMock } from '../../../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../../../authorization/alerting_authorization.mock';
|
||||
|
@ -43,6 +47,10 @@ jest.mock('uuid', () => {
|
|||
return { v4: () => `${uuid++}` };
|
||||
});
|
||||
|
||||
jest.mock('../get_schedule_frequency', () => ({
|
||||
validateScheduleLimit: jest.fn(),
|
||||
}));
|
||||
|
||||
const taskManager = taskManagerMock.createStart();
|
||||
const ruleTypeRegistry = ruleTypeRegistryMock.create();
|
||||
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
|
||||
|
@ -50,6 +58,7 @@ 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> = {
|
||||
|
@ -63,11 +72,13 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
auditLogger,
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
|
|
|
@ -36,6 +36,7 @@ import { RuleAttributes } from '../../../../data/rule/types';
|
|||
import type { CreateRuleData } from './types';
|
||||
import { createRuleDataSchema } from './schemas';
|
||||
import { createRuleSavedObject } from '../../../../rules_client/lib';
|
||||
import { validateScheduleLimit } from '../get_schedule_frequency';
|
||||
|
||||
export interface CreateRuleOptions {
|
||||
id?: string;
|
||||
|
@ -60,6 +61,12 @@ export async function createRule<Params extends RuleParams = never>(
|
|||
|
||||
try {
|
||||
createRuleDataSchema.validate(data);
|
||||
if (data.enabled) {
|
||||
await validateScheduleLimit({
|
||||
context,
|
||||
updatedInterval: data.schedule.interval,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
throw Boom.badRequest(`Error validating create data - ${error.message}`);
|
||||
}
|
||||
|
|
|
@ -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."`
|
||||
);
|
||||
});
|
||||
});
|
|
@ -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.`
|
||||
);
|
||||
}
|
||||
};
|
|
@ -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';
|
|
@ -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 }),
|
||||
});
|
|
@ -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';
|
|
@ -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>;
|
|
@ -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';
|
|
@ -23,6 +23,7 @@ describe('config validation', () => {
|
|||
},
|
||||
"maxEphemeralActionsPerAlert": 10,
|
||||
"rules": Object {
|
||||
"maxScheduledPerMinute": 10000,
|
||||
"minimumScheduleInterval": Object {
|
||||
"enforce": false,
|
||||
"value": "1m",
|
||||
|
|
|
@ -38,6 +38,7 @@ const rulesSchema = schema.object({
|
|||
}),
|
||||
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({
|
||||
timeout: schema.maybe(schema.string({ validate: validateDurationSchema })),
|
||||
actions: schema.object({
|
||||
|
@ -70,7 +71,10 @@ export const configSchema = schema.object({
|
|||
|
||||
export type AlertingConfig = TypeOf<typeof configSchema>;
|
||||
export type RulesConfig = TypeOf<typeof rulesSchema>;
|
||||
export type AlertingRulesConfig = Pick<AlertingConfig['rules'], 'minimumScheduleInterval'> & {
|
||||
export type AlertingRulesConfig = Pick<
|
||||
AlertingConfig['rules'],
|
||||
'minimumScheduleInterval' | 'maxScheduledPerMinute'
|
||||
> & {
|
||||
isUsingSecurity: boolean;
|
||||
};
|
||||
export type ActionsConfig = RulesConfig['run']['actions'];
|
||||
|
|
|
@ -139,6 +139,7 @@ describe('Alerting Plugin', () => {
|
|||
await waitForSetupComplete(setupMocks);
|
||||
|
||||
expect(setupContract.getConfig()).toEqual({
|
||||
maxScheduledPerMinute: 10000,
|
||||
isUsingSecurity: false,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
});
|
||||
|
|
|
@ -415,7 +415,7 @@ export class AlertingPlugin {
|
|||
},
|
||||
getConfig: () => {
|
||||
return {
|
||||
...pick(this.config.rules, 'minimumScheduleInterval'),
|
||||
...pick(this.config.rules, ['minimumScheduleInterval', 'maxScheduledPerMinute']),
|
||||
isUsingSecurity: this.licenseState ? !!this.licenseState.getIsSecurityEnabled() : false,
|
||||
};
|
||||
},
|
||||
|
@ -481,6 +481,7 @@ export class AlertingPlugin {
|
|||
taskManager: plugins.taskManager,
|
||||
securityPluginSetup: security,
|
||||
securityPluginStart: plugins.security,
|
||||
internalSavedObjectsRepository: core.savedObjects.createInternalRepository(['alert']),
|
||||
encryptedSavedObjectsClient,
|
||||
spaceIdToNamespace,
|
||||
getSpaceId(request: KibanaRequest) {
|
||||
|
@ -492,6 +493,7 @@ export class AlertingPlugin {
|
|||
authorization: alertingAuthorizationClientFactory,
|
||||
eventLogger: this.eventLogger,
|
||||
minimumScheduleInterval: this.config.rules.minimumScheduleInterval,
|
||||
maxScheduledPerMinute: this.config.rules.maxScheduledPerMinute,
|
||||
});
|
||||
|
||||
rulesSettingsClientFactory.initialize({
|
||||
|
|
|
@ -47,6 +47,7 @@ import { cloneRuleRoute } from './clone_rule';
|
|||
import { getFlappingSettingsRoute } from './get_flapping_settings';
|
||||
import { updateFlappingSettingsRoute } from './update_flapping_settings';
|
||||
import { getRuleTagsRoute } from './get_rule_tags';
|
||||
import { getScheduleFrequencyRoute } from './rule/apis/get_schedule_frequency';
|
||||
|
||||
import { createMaintenanceWindowRoute } from './maintenance_window/create_maintenance_window';
|
||||
import { getMaintenanceWindowRoute } from './maintenance_window/get_maintenance_window';
|
||||
|
@ -129,4 +130,5 @@ export function defineRoutes(opts: RouteOptions) {
|
|||
registerRulesValueSuggestionsRoute(router, licenseState, config$!);
|
||||
registerFieldsRoute(router, licenseState);
|
||||
bulkGetMaintenanceWindowRoute(router, licenseState);
|
||||
getScheduleFrequencyRoute(router, licenseState);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
})
|
||||
)
|
||||
);
|
||||
};
|
|
@ -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';
|
|
@ -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';
|
|
@ -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';
|
|
@ -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,
|
||||
};
|
||||
};
|
|
@ -52,6 +52,7 @@ const createRulesClientMock = () => {
|
|||
runSoon: jest.fn(),
|
||||
clone: jest.fn(),
|
||||
getAlertFromRaw: jest.fn(),
|
||||
getScheduleFrequency: jest.fn(),
|
||||
};
|
||||
return mocked;
|
||||
};
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
* 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 { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
|
||||
|
@ -24,6 +28,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
|
|||
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
|
||||
const authorization = alertingAuthorizationMock.create();
|
||||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v8.0.0';
|
||||
const rulesClientParams: jest.Mocked<RulesClientContext> = {
|
||||
|
@ -36,10 +41,12 @@ const rulesClientParams: jest.Mocked<RulesClientContext> = {
|
|||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
minimumScheduleIntervalInMs: 1,
|
||||
fieldsToExcludeFromPublicApi: [],
|
||||
|
|
|
@ -4,9 +4,10 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import pMap from 'p-map';
|
||||
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 { Logger } from '@kbn/core/server';
|
||||
import { TaskManagerStartContract, TaskStatus } from '@kbn/task-manager-plugin/server';
|
||||
|
@ -28,6 +29,7 @@ import {
|
|||
migrateLegacyActions,
|
||||
} from '../lib';
|
||||
import { RulesClientContext, BulkOperationError, BulkOptions } from '../types';
|
||||
import { validateScheduleLimit } from '../../application/rule/methods/get_schedule_frequency';
|
||||
|
||||
const getShouldScheduleTask = async (
|
||||
context: RulesClientContext,
|
||||
|
@ -121,116 +123,136 @@ const bulkEnableRulesWithOCC = async (
|
|||
)
|
||||
);
|
||||
|
||||
const rulesFinderRules: Array<SavedObjectsFindResult<RawRule>> = [];
|
||||
const rulesToEnable: Array<SavedObjectsBulkUpdateObject<RawRule>> = [];
|
||||
const errors: BulkOperationError[] = [];
|
||||
const ruleNameToRuleIdMapping: Record<string, string> = {};
|
||||
const username = await context.getUserName();
|
||||
let scheduleValidationError = '';
|
||||
|
||||
await withSpan(
|
||||
{ name: 'Get rules, collect them and their attributes', type: 'rules' },
|
||||
async () => {
|
||||
for await (const response of rulesFinder.find()) {
|
||||
await pMap(response.saved_objects, async (rule) => {
|
||||
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,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
rulesFinderRules.push(...response.saved_objects);
|
||||
}
|
||||
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,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import Boom from '@hapi/boom';
|
||||
import type { SavedObjectReference } from '@kbn/core/server';
|
||||
import { TaskStatus } from '@kbn/task-manager-plugin/server';
|
||||
import { RawRule, IntervalSchedule } from '../../types';
|
||||
|
@ -13,6 +14,7 @@ import { retryIfConflicts } from '../../lib/retry_if_conflicts';
|
|||
import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events';
|
||||
import { RulesClientContext } from '../types';
|
||||
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> {
|
||||
return await retryIfConflicts(
|
||||
|
@ -46,6 +48,15 @@ async function enableWithOCC(context: RulesClientContext, { id }: { id: string }
|
|||
references = alert.references;
|
||||
}
|
||||
|
||||
try {
|
||||
await validateScheduleLimit({
|
||||
context,
|
||||
updatedInterval: attributes.schedule.interval,
|
||||
});
|
||||
} catch (error) {
|
||||
throw Boom.badRequest(`Error validating enable rule data - ${error.message}`);
|
||||
}
|
||||
|
||||
try {
|
||||
await context.authorization.ensureAuthorized({
|
||||
ruleTypeId: attributes.alertTypeId,
|
||||
|
|
|
@ -33,6 +33,7 @@ import {
|
|||
createNewAPIKeySet,
|
||||
migrateLegacyActions,
|
||||
} from '../lib';
|
||||
import { validateScheduleLimit } from '../../application/rule/methods/get_schedule_frequency';
|
||||
|
||||
type ShouldIncrementRevision = (params?: RuleTypeParams) => boolean;
|
||||
|
||||
|
@ -88,6 +89,21 @@ async function updateWithOCC<Params extends RuleTypeParams>(
|
|||
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 {
|
||||
await context.authorization.ensureAuthorized({
|
||||
ruleTypeId: alertSavedObject.attributes.alertTypeId,
|
||||
|
|
|
@ -57,6 +57,7 @@ import { runSoon } from './methods/run_soon';
|
|||
import { listRuleTypes } from './methods/list_rule_types';
|
||||
import { getAlertFromRaw, GetAlertFromRawParams } from './lib/get_alert_from_raw';
|
||||
import { getTags, GetTagsParams } from './methods/get_tags';
|
||||
import { getScheduleFrequency } from '../application/rule/methods/get_schedule_frequency/get_schedule_frequency';
|
||||
|
||||
export type ConstructorOptions = Omit<
|
||||
RulesClientContext,
|
||||
|
@ -179,6 +180,8 @@ export class RulesClient {
|
|||
|
||||
public getTags = (params: GetTagsParams) => getTags(this.context, params);
|
||||
|
||||
public getScheduleFrequency = () => getScheduleFrequency(this.context);
|
||||
|
||||
public getAlertFromRaw = (params: GetAlertFromRawParams) =>
|
||||
getAlertFromRaw(
|
||||
this.context,
|
||||
|
|
|
@ -6,7 +6,11 @@
|
|||
*/
|
||||
|
||||
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 { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
|
||||
|
@ -32,12 +36,14 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
|
|||
const authorization = alertingAuthorizationMock.create();
|
||||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const auditLogger = auditLoggerMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v7.10.0';
|
||||
const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
||||
taskManager,
|
||||
ruleTypeRegistry,
|
||||
unsecuredSavedObjectsClient,
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
authorization: authorization as unknown as AlertingAuthorization,
|
||||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
|
@ -46,6 +52,7 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
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 { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
|
||||
|
@ -53,6 +53,7 @@ const authorization = alertingAuthorizationMock.create();
|
|||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const auditLogger = auditLoggerMock.create();
|
||||
const logger = loggerMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v8.2.0';
|
||||
const createAPIKeyMock = jest.fn();
|
||||
|
@ -67,11 +68,13 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getUserName: jest.fn(),
|
||||
createAPIKey: createAPIKeyMock,
|
||||
logger,
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
auditLogger,
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
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 type { SavedObject } from '@kbn/core-saved-objects-server';
|
||||
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
|
@ -61,6 +61,7 @@ const actionsAuthorization = actionsAuthorizationMock.create();
|
|||
const auditLogger = auditLoggerMock.create();
|
||||
const logger = loggerMock.create();
|
||||
const eventLogger = eventLoggerMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v8.2.0';
|
||||
const createAPIKeyMock = jest.fn();
|
||||
|
@ -75,12 +76,14 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getUserName: jest.fn(),
|
||||
createAPIKey: createAPIKeyMock,
|
||||
logger,
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
auditLogger,
|
||||
eventLogger,
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
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 { ruleTypeRegistryMock } from '../../rule_type_registry.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(),
|
||||
}));
|
||||
|
||||
jest.mock('../../application/rule/methods/get_schedule_frequency', () => ({
|
||||
validateScheduleLimit: jest.fn(),
|
||||
}));
|
||||
|
||||
const taskManager = taskManagerMock.createStart();
|
||||
const ruleTypeRegistry = ruleTypeRegistryMock.create();
|
||||
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
|
||||
|
@ -53,6 +57,7 @@ const authorization = alertingAuthorizationMock.create();
|
|||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const auditLogger = auditLoggerMock.create();
|
||||
const logger = loggerMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v8.2.0';
|
||||
const createAPIKeyMock = jest.fn();
|
||||
|
@ -67,11 +72,13 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getUserName: jest.fn(),
|
||||
createAPIKey: createAPIKeyMock,
|
||||
logger,
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
auditLogger,
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
|
|
|
@ -8,7 +8,11 @@
|
|||
import moment from 'moment';
|
||||
import sinon from 'sinon';
|
||||
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 { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
|
||||
|
@ -40,6 +44,7 @@ const authorization = alertingAuthorizationMock.create();
|
|||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const auditLogger = auditLoggerMock.create();
|
||||
const eventLogger = eventLoggerMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v7.10.0';
|
||||
const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
||||
|
@ -50,10 +55,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
|
|
|
@ -8,7 +8,11 @@
|
|||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
|
||||
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 { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
|
||||
|
@ -43,12 +47,14 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
|
|||
const authorization = alertingAuthorizationMock.create();
|
||||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const auditLogger = auditLoggerMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v7.10.0';
|
||||
const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
||||
taskManager,
|
||||
ruleTypeRegistry,
|
||||
unsecuredSavedObjectsClient,
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
authorization: authorization as unknown as AlertingAuthorization,
|
||||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
|
@ -57,6 +63,7 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
|
|
|
@ -7,7 +7,11 @@
|
|||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
|
||||
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 { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
|
||||
|
@ -44,6 +48,7 @@ const authorization = alertingAuthorizationMock.create();
|
|||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const auditLogger = auditLoggerMock.create();
|
||||
const eventLogger = eventLoggerMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v7.10.0';
|
||||
const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
||||
|
@ -54,10 +59,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
|
|
|
@ -7,7 +7,11 @@
|
|||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
|
||||
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 { ruleTypeRegistryMock } from '../../rule_type_registry.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(),
|
||||
}));
|
||||
|
||||
jest.mock('../../application/rule/methods/get_schedule_frequency', () => ({
|
||||
validateScheduleLimit: jest.fn(),
|
||||
}));
|
||||
|
||||
const taskManager = taskManagerMock.createStart();
|
||||
const ruleTypeRegistry = ruleTypeRegistryMock.create();
|
||||
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
|
||||
|
@ -38,6 +46,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
|
|||
const authorization = alertingAuthorizationMock.create();
|
||||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const auditLogger = auditLoggerMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v7.10.0';
|
||||
const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
||||
|
@ -48,10 +57,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
|
|
|
@ -6,7 +6,11 @@
|
|||
*/
|
||||
|
||||
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 { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
|
||||
|
@ -36,6 +40,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
|
|||
const authorization = alertingAuthorizationMock.create();
|
||||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const auditLogger = auditLoggerMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v7.10.0';
|
||||
const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
||||
|
@ -46,10 +51,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
|
|
|
@ -7,7 +7,11 @@
|
|||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
|
||||
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 { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
|
||||
|
@ -33,6 +37,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
|
|||
const authorization = alertingAuthorizationMock.create();
|
||||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const auditLogger = auditLoggerMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v7.10.0';
|
||||
const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
||||
|
@ -43,10 +48,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
|
|
|
@ -7,7 +7,11 @@
|
|||
|
||||
import { RulesClient, ConstructorOptions } from '../rules_client';
|
||||
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 { fromKueryExpression } from '@kbn/es-query';
|
||||
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
|
@ -31,6 +35,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
|
|||
const authorization = alertingAuthorizationMock.create();
|
||||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const auditLogger = auditLoggerMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v7.10.0';
|
||||
const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
||||
|
@ -41,10 +46,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
|
|
|
@ -6,7 +6,11 @@
|
|||
*/
|
||||
|
||||
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 { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
|
||||
|
@ -24,6 +28,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
|
|||
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
|
||||
const authorization = alertingAuthorizationMock.create();
|
||||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v7.10.0';
|
||||
const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
||||
|
@ -34,10 +39,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
|
|
|
@ -7,7 +7,11 @@
|
|||
|
||||
import { omit, mean } from 'lodash';
|
||||
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 { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
|
||||
|
@ -30,6 +34,7 @@ const eventLogClient = eventLogClientMock.create();
|
|||
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
|
||||
const authorization = alertingAuthorizationMock.create();
|
||||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v7.10.0';
|
||||
const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
||||
|
@ -40,10 +45,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
|
|
|
@ -7,7 +7,11 @@
|
|||
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
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 { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
|
||||
|
@ -32,6 +36,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
|
|||
const authorization = alertingAuthorizationMock.create();
|
||||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const auditLogger = auditLoggerMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v7.10.0';
|
||||
const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
||||
|
@ -42,10 +47,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
|
|
|
@ -6,7 +6,11 @@
|
|||
*/
|
||||
import { v4 } from 'uuid';
|
||||
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 { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
|
||||
|
@ -27,12 +31,14 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
|
|||
const authorization = alertingAuthorizationMock.create();
|
||||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const auditLogger = auditLoggerMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v7.10.0';
|
||||
const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
||||
taskManager,
|
||||
ruleTypeRegistry,
|
||||
unsecuredSavedObjectsClient,
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
authorization: authorization as unknown as AlertingAuthorization,
|
||||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
|
@ -41,6 +47,7 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
|
|
|
@ -6,7 +6,11 @@
|
|||
*/
|
||||
|
||||
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 { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
|
||||
|
@ -28,6 +32,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
|
|||
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
|
||||
const authorization = alertingAuthorizationMock.create();
|
||||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v7.10.0';
|
||||
const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
||||
|
@ -38,10 +43,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
|
|
|
@ -6,7 +6,11 @@
|
|||
*/
|
||||
|
||||
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 { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
|
||||
|
@ -24,6 +28,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
|
|||
const authorization = alertingAuthorizationMock.create();
|
||||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const auditLogger = auditLoggerMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v7.10.0';
|
||||
const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
||||
|
@ -34,10 +39,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
|
|
|
@ -6,7 +6,11 @@
|
|||
*/
|
||||
|
||||
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 { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
|
||||
|
@ -24,6 +28,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
|
|||
const authorization = alertingAuthorizationMock.create();
|
||||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const auditLogger = auditLoggerMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v7.10.0';
|
||||
const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
||||
|
@ -34,10 +39,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
|
|
|
@ -7,7 +7,11 @@
|
|||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
|
||||
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 { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
|
||||
|
@ -33,6 +37,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
|
|||
const authorization = alertingAuthorizationMock.create();
|
||||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const auditLogger = auditLoggerMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v7.10.0';
|
||||
const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
||||
|
@ -43,10 +48,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
|
|
|
@ -6,7 +6,11 @@
|
|||
*/
|
||||
|
||||
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 { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
|
||||
|
@ -25,6 +29,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
|
|||
const authorization = alertingAuthorizationMock.create();
|
||||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const auditLogger = auditLoggerMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v7.10.0';
|
||||
const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
||||
|
@ -35,10 +40,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
|
|
|
@ -6,7 +6,11 @@
|
|||
*/
|
||||
|
||||
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 { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
|
||||
|
@ -24,6 +28,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
|
|||
const authorization = alertingAuthorizationMock.create();
|
||||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const auditLogger = auditLoggerMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v7.10.0';
|
||||
const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
||||
|
@ -34,10 +39,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
|
|
|
@ -6,7 +6,11 @@
|
|||
*/
|
||||
|
||||
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 { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
|
||||
|
@ -24,6 +28,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
|
|||
const authorization = alertingAuthorizationMock.create();
|
||||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const auditLogger = auditLoggerMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v7.10.0';
|
||||
const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
||||
|
@ -34,10 +39,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
|
|
|
@ -9,7 +9,11 @@ import { v4 as uuidv4 } from 'uuid';
|
|||
import { schema } from '@kbn/config-schema';
|
||||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
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 { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
|
||||
|
@ -50,6 +54,10 @@ jest.mock('uuid', () => {
|
|||
return { v4: () => `${uuid++}` };
|
||||
});
|
||||
|
||||
jest.mock('../../application/rule/methods/get_schedule_frequency', () => ({
|
||||
validateScheduleLimit: jest.fn(),
|
||||
}));
|
||||
|
||||
const bulkMarkApiKeysForInvalidationMock = bulkMarkApiKeysForInvalidation as jest.Mock;
|
||||
const taskManager = taskManagerMock.createStart();
|
||||
const ruleTypeRegistry = ruleTypeRegistryMock.create();
|
||||
|
@ -58,6 +66,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
|
|||
const authorization = alertingAuthorizationMock.create();
|
||||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const auditLogger = auditLoggerMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v7.10.0';
|
||||
const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
||||
|
@ -71,11 +80,13 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
auditLogger,
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
|
|
|
@ -6,7 +6,11 @@
|
|||
*/
|
||||
|
||||
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 { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
|
||||
|
@ -30,6 +34,7 @@ const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
|
|||
const authorization = alertingAuthorizationMock.create();
|
||||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const auditLogger = auditLoggerMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v7.10.0';
|
||||
const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
||||
|
@ -40,10 +45,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
|
|
|
@ -6,7 +6,12 @@
|
|||
*/
|
||||
|
||||
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 {
|
||||
GrantAPIKeyResult as SecurityPluginGrantAPIKeyResult,
|
||||
|
@ -55,11 +60,13 @@ export interface RulesClientContext {
|
|||
readonly authorization: AlertingAuthorization;
|
||||
readonly ruleTypeRegistry: RuleTypeRegistry;
|
||||
readonly minimumScheduleInterval: AlertingRulesConfig['minimumScheduleInterval'];
|
||||
readonly maxScheduledPerMinute: AlertingRulesConfig['maxScheduledPerMinute'];
|
||||
readonly minimumScheduleIntervalInMs: number;
|
||||
readonly createAPIKey: (name: string) => Promise<CreateAPIKeyResult>;
|
||||
readonly getActionsClient: () => Promise<ActionsClient>;
|
||||
readonly actionsAuthorization: ActionsAuthorization;
|
||||
readonly getEventLogClient: () => Promise<IEventLogClient>;
|
||||
readonly internalSavedObjectsRepository: ISavedObjectsRepository;
|
||||
readonly encryptedSavedObjectsClient: EncryptedSavedObjectsClient;
|
||||
readonly kibanaVersion: PluginInitializerContext['env']['packageInfo']['version'];
|
||||
readonly auditLogger?: AuditLogger;
|
||||
|
|
|
@ -8,7 +8,11 @@
|
|||
import { cloneDeep } from 'lodash';
|
||||
|
||||
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 { ruleTypeRegistryMock } from './rule_type_registry.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 { RecoveredActionGroup } from '../common';
|
||||
|
||||
jest.mock('./application/rule/methods/get_schedule_frequency', () => ({
|
||||
validateScheduleLimit: jest.fn(),
|
||||
}));
|
||||
|
||||
let rulesClient: RulesClient;
|
||||
|
||||
const MockAlertId = 'alert-id';
|
||||
|
@ -34,6 +42,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
|
|||
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
|
||||
const authorization = alertingAuthorizationMock.create();
|
||||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const kibanaVersion = 'v7.10.0';
|
||||
const logger = loggingSystemMock.create().get();
|
||||
|
@ -48,10 +57,12 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger,
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
savedObjectsClientMock,
|
||||
savedObjectsServiceMock,
|
||||
loggingSystemMock,
|
||||
savedObjectsRepositoryMock,
|
||||
} from '@kbn/core/server/mocks';
|
||||
import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks';
|
||||
import { AuthenticatedUser } from '@kbn/security-plugin/common/model';
|
||||
|
@ -37,6 +38,7 @@ const securityPluginStart = securityMock.createStart();
|
|||
|
||||
const alertingAuthorization = alertingAuthorizationMock.create();
|
||||
const alertingAuthorizationClientFactory = alertingAuthorizationClientFactoryMock.createFactory();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
|
||||
const rulesClientFactoryParams: jest.Mocked<RulesClientFactoryOpts> = {
|
||||
logger: loggingSystemMock.create().get(),
|
||||
|
@ -44,7 +46,9 @@ const rulesClientFactoryParams: jest.Mocked<RulesClientFactoryOpts> = {
|
|||
ruleTypeRegistry: ruleTypeRegistryMock.create(),
|
||||
getSpaceId: jest.fn(),
|
||||
spaceIdToNamespace: jest.fn(),
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: encryptedSavedObjectsMock.createClient(),
|
||||
actions: actionsMock.createStart(),
|
||||
eventLog: eventLogMock.createStart(),
|
||||
|
@ -101,8 +105,10 @@ test('creates a rules client with proper constructor arguments when security is
|
|||
getActionsClient: expect.any(Function),
|
||||
getEventLogClient: expect.any(Function),
|
||||
createAPIKey: expect.any(Function),
|
||||
internalSavedObjectsRepository: rulesClientFactoryParams.internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: rulesClientFactoryParams.encryptedSavedObjectsClient,
|
||||
kibanaVersion: '7.10.0',
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
isAuthenticationTypeAPIKey: expect.any(Function),
|
||||
getAuthenticationAPIKey: expect.any(Function),
|
||||
|
@ -139,10 +145,12 @@ test('creates a rules client with proper constructor arguments', async () => {
|
|||
namespace: 'default',
|
||||
getUserName: expect.any(Function),
|
||||
createAPIKey: expect.any(Function),
|
||||
internalSavedObjectsRepository: rulesClientFactoryParams.internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: rulesClientFactoryParams.encryptedSavedObjectsClient,
|
||||
getActionsClient: expect.any(Function),
|
||||
getEventLogClient: expect.any(Function),
|
||||
kibanaVersion: '7.10.0',
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
isAuthenticationTypeAPIKey: expect.any(Function),
|
||||
getAuthenticationAPIKey: expect.any(Function),
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
Logger,
|
||||
SavedObjectsServiceStart,
|
||||
PluginInitializerContext,
|
||||
ISavedObjectsRepository,
|
||||
} from '@kbn/core/server';
|
||||
import { PluginStartContract as ActionsPluginStartContract } from '@kbn/actions-plugin/server';
|
||||
import {
|
||||
|
@ -34,12 +35,14 @@ export interface RulesClientFactoryOpts {
|
|||
getSpaceId: (request: KibanaRequest) => string;
|
||||
spaceIdToNamespace: SpaceIdToNamespaceFunction;
|
||||
encryptedSavedObjectsClient: EncryptedSavedObjectsClient;
|
||||
internalSavedObjectsRepository: ISavedObjectsRepository;
|
||||
actions: ActionsPluginStartContract;
|
||||
eventLog: IEventLogClientService;
|
||||
kibanaVersion: PluginInitializerContext['env']['packageInfo']['version'];
|
||||
authorization: AlertingAuthorizationClientFactory;
|
||||
eventLogger?: IEventLogger;
|
||||
minimumScheduleInterval: AlertingRulesConfig['minimumScheduleInterval'];
|
||||
maxScheduledPerMinute: AlertingRulesConfig['maxScheduledPerMinute'];
|
||||
}
|
||||
|
||||
export class RulesClientFactory {
|
||||
|
@ -52,12 +55,14 @@ export class RulesClientFactory {
|
|||
private getSpaceId!: (request: KibanaRequest) => string;
|
||||
private spaceIdToNamespace!: SpaceIdToNamespaceFunction;
|
||||
private encryptedSavedObjectsClient!: EncryptedSavedObjectsClient;
|
||||
private internalSavedObjectsRepository!: ISavedObjectsRepository;
|
||||
private actions!: ActionsPluginStartContract;
|
||||
private eventLog!: IEventLogClientService;
|
||||
private kibanaVersion!: PluginInitializerContext['env']['packageInfo']['version'];
|
||||
private authorization!: AlertingAuthorizationClientFactory;
|
||||
private eventLogger?: IEventLogger;
|
||||
private minimumScheduleInterval!: AlertingRulesConfig['minimumScheduleInterval'];
|
||||
private maxScheduledPerMinute!: AlertingRulesConfig['maxScheduledPerMinute'];
|
||||
|
||||
public initialize(options: RulesClientFactoryOpts) {
|
||||
if (this.isInitialized) {
|
||||
|
@ -72,12 +77,14 @@ export class RulesClientFactory {
|
|||
this.securityPluginStart = options.securityPluginStart;
|
||||
this.spaceIdToNamespace = options.spaceIdToNamespace;
|
||||
this.encryptedSavedObjectsClient = options.encryptedSavedObjectsClient;
|
||||
this.internalSavedObjectsRepository = options.internalSavedObjectsRepository;
|
||||
this.actions = options.actions;
|
||||
this.eventLog = options.eventLog;
|
||||
this.kibanaVersion = options.kibanaVersion;
|
||||
this.authorization = options.authorization;
|
||||
this.eventLogger = options.eventLogger;
|
||||
this.minimumScheduleInterval = options.minimumScheduleInterval;
|
||||
this.maxScheduledPerMinute = options.maxScheduledPerMinute;
|
||||
}
|
||||
|
||||
public create(request: KibanaRequest, savedObjects: SavedObjectsServiceStart): RulesClient {
|
||||
|
@ -95,6 +102,7 @@ export class RulesClientFactory {
|
|||
taskManager: this.taskManager,
|
||||
ruleTypeRegistry: this.ruleTypeRegistry,
|
||||
minimumScheduleInterval: this.minimumScheduleInterval,
|
||||
maxScheduledPerMinute: this.maxScheduledPerMinute,
|
||||
unsecuredSavedObjectsClient: savedObjects.getScopedClient(request, {
|
||||
excludedExtensions: [SECURITY_EXTENSION_ID],
|
||||
includedHiddenTypes: ['alert', 'api_key_pending_invalidation'],
|
||||
|
@ -102,6 +110,7 @@ export class RulesClientFactory {
|
|||
authorization: this.authorization.create(request),
|
||||
actionsAuthorization: actions.getActionsAuthorizationWithRequest(request),
|
||||
namespace: this.spaceIdToNamespace(spaceId),
|
||||
internalSavedObjectsRepository: this.internalSavedObjectsRepository,
|
||||
encryptedSavedObjectsClient: this.encryptedSavedObjectsClient,
|
||||
auditLogger: securityPluginSetup?.audit.asScoped(request),
|
||||
async getUserName() {
|
||||
|
|
|
@ -60,6 +60,7 @@ export function generateAlertingConfig(): AlertingConfig {
|
|||
maxEphemeralActionsPerAlert: 10,
|
||||
cancelAlertsOnRuleTimeout: true,
|
||||
rules: {
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
run: {
|
||||
actions: {
|
||||
|
|
|
@ -51,6 +51,7 @@ describe('createConfigRoute', () => {
|
|||
baseRoute: `/internal/triggers_actions_ui`,
|
||||
alertingConfig: () => ({
|
||||
isUsingSecurity: true,
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
}),
|
||||
getRulesClientWithRequest: () => mockRulesClient,
|
||||
|
@ -64,7 +65,11 @@ describe('createConfigRoute', () => {
|
|||
|
||||
expect(mockResponse.ok).toBeCalled();
|
||||
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`,
|
||||
alertingConfig: () => ({
|
||||
isUsingSecurity: true,
|
||||
maxScheduledPerMinute: 10000,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
}),
|
||||
getRulesClientWithRequest: () => mockRulesClient,
|
||||
|
|
|
@ -28,6 +28,7 @@ interface CreateTestConfigOptions {
|
|||
reportName?: string;
|
||||
useDedicatedTaskRunner: boolean;
|
||||
enableFooterInEmail?: boolean;
|
||||
maxScheduledPerMinute?: number;
|
||||
}
|
||||
|
||||
// test.not-enabled is specifically not enabled
|
||||
|
@ -82,6 +83,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
|
|||
reportName = undefined,
|
||||
useDedicatedTaskRunner,
|
||||
enableFooterInEmail = true,
|
||||
maxScheduledPerMinute,
|
||||
} = options;
|
||||
|
||||
return async ({ readConfigFile }: FtrConfigProviderContext) => {
|
||||
|
@ -151,6 +153,11 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
|
|||
? [`--xpack.actions.email.domain_allowlist=${JSON.stringify(emailDomainsAllowed)}`]
|
||||
: [];
|
||||
|
||||
const maxScheduledPerMinuteSettings =
|
||||
typeof maxScheduledPerMinute === 'number'
|
||||
? [`--xpack.alerting.rules.maxScheduledPerMinute=${maxScheduledPerMinute}`]
|
||||
: [];
|
||||
|
||||
return {
|
||||
testFiles: testFiles ? testFiles : [require.resolve(`../${name}/tests/`)],
|
||||
servers,
|
||||
|
@ -199,6 +206,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
|
|||
...actionsProxyUrl,
|
||||
...customHostSettings,
|
||||
...emailSettings,
|
||||
...maxScheduledPerMinuteSettings,
|
||||
'--xpack.eventLog.logEntries=true',
|
||||
'--xpack.task_manager.ephemeral_tasks.enabled=false',
|
||||
`--xpack.task_manager.unsafe.exclude_task_types=${JSON.stringify([
|
||||
|
|
|
@ -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,
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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.'
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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.'
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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)}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
|
@ -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'));
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue