Skip to content

Commit

Permalink
Merge branch 'main' into prefer-throughput-as-tiebreaker
Browse files Browse the repository at this point in the history
  • Loading branch information
kibanamachine authored Mar 28, 2022
2 parents e0fa9aa + c9aad65 commit e097e62
Show file tree
Hide file tree
Showing 32 changed files with 872 additions and 257 deletions.
26 changes: 19 additions & 7 deletions docs/settings/alert-action-settings.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -185,13 +185,6 @@ For example, `20m`, `24h`, `7d`, `1w`. Default: `60s`.
`xpack.alerting.maxEphemeralActionsPerAlert`::
Sets the number of actions that will be executed ephemerally. To use this, enable ephemeral tasks in task manager first with <<task-manager-settings,`xpack.task_manager.ephemeral_tasks.enabled`>>

`xpack.alerting.defaultRuleTaskTimeout`::
Specifies the default timeout for the all rule types tasks. The time is formatted as:
+
`<count>[ms,s,m,h,d,w,M,Y]`
+
For example, `20m`, `24h`, `7d`, `1w`. Default: `5m`.

`xpack.alerting.cancelAlertsOnRuleTimeout`::
Specifies whether to skip writing alerts and scheduling actions if rule execution is cancelled due to timeout. Default: `true`. This setting can be overridden by individual rule types.

Expand All @@ -207,3 +200,22 @@ Specifies the behavior when a new or changed rule has a schedule interval less t

`xpack.alerting.rules.execution.actions.max`::
Specifies the maximum number of actions that a rule can trigger each time detection checks run.

`xpack.alerting.rules.execution.timeout`::
Specifies the default timeout for tasks associated with all types of rules. The time is formatted as:
+
`<count>[ms,s,m,h,d,w,M,Y]`
+
For example, `20m`, `24h`, `7d`, `1w`. Default: `5m`.

`xpack.alerting.rules.execution.ruleTypeOverrides`::
Overrides the configs under `xpack.alerting.rules.execution` for the rule type with the given ID. List the rule identifier and its settings in an array of objects.
+
For example:
```
xpack.alerting.rules.execution:
timeout: '5m'
ruleTypeOverrides:
- id: '.index-threshold'
timeout: '15m'
```
2 changes: 1 addition & 1 deletion packages/kbn-test/jest-preset.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module.exports = {
coverageDirectory: '<rootDir>/target/kibana-coverage/jest',

// An array of regexp pattern strings used to skip coverage collection
coveragePathIgnorePatterns: ['/node_modules/', '.*\\.d\\.ts'],
coveragePathIgnorePatterns: ['/node_modules/', '.*\\.d\\.ts', 'jest\\.config\\.js'],

// A list of reporter names that Jest uses when writing coverage reports
coverageReporters: !!process.env.CODE_COVERAGE
Expand Down
50 changes: 50 additions & 0 deletions src/core/server/status/cached_plugins_status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { Observable } from 'rxjs';

import { type PluginName } from '../plugins';
import { type ServiceStatus } from './types';

import { type Deps, PluginsStatusService as BasePluginsStatusService } from './plugins_status';

export class PluginsStatusService extends BasePluginsStatusService {
private all$?: Observable<Record<PluginName, ServiceStatus>>;
private dependenciesStatuses$: Record<PluginName, Observable<Record<PluginName, ServiceStatus>>>;
private derivedStatuses$: Record<PluginName, Observable<ServiceStatus>>;

constructor(deps: Deps) {
super(deps);
this.dependenciesStatuses$ = {};
this.derivedStatuses$ = {};
}

public getAll$(): Observable<Record<PluginName, ServiceStatus>> {
if (!this.all$) {
this.all$ = super.getAll$();
}

return this.all$;
}

public getDependenciesStatus$(plugin: PluginName): Observable<Record<PluginName, ServiceStatus>> {
if (!this.dependenciesStatuses$[plugin]) {
this.dependenciesStatuses$[plugin] = super.getDependenciesStatus$(plugin);
}

return this.dependenciesStatuses$[plugin];
}

public getDerivedStatus$(plugin: PluginName): Observable<ServiceStatus> {
if (!this.derivedStatuses$[plugin]) {
this.derivedStatuses$[plugin] = super.getDerivedStatus$(plugin);
}

return this.derivedStatuses$[plugin];
}
}
41 changes: 23 additions & 18 deletions src/core/server/status/plugins_status.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { PluginName } from '../plugins';
import { PluginsStatusService } from './plugins_status';
import { of, Observable, BehaviorSubject, ReplaySubject } from 'rxjs';
import { ServiceStatusLevels, CoreStatus, ServiceStatus } from './types';
import { first } from 'rxjs/operators';
import { first, skip } from 'rxjs/operators';
import { ServiceStatusLevelSnapshotSerializer } from './test_utils';

expect.addSnapshotSerializer(ServiceStatusLevelSnapshotSerializer);
Expand Down Expand Up @@ -215,7 +215,7 @@ describe('PluginStatusService', () => {
service.set('a', of({ level: ServiceStatusLevels.available, summary: 'a status' }));

expect(await service.getAll$().pipe(first()).toPromise()).toEqual({
a: { level: ServiceStatusLevels.available, summary: 'a status' }, // a is available depsite savedObjects being degraded
a: { level: ServiceStatusLevels.available, summary: 'a status' }, // a is available despite savedObjects being degraded
b: {
level: ServiceStatusLevels.degraded,
summary: '1 service is degraded: savedObjects',
Expand All @@ -239,6 +239,10 @@ describe('PluginStatusService', () => {
const statusUpdates: Array<Record<PluginName, ServiceStatus>> = [];
const subscription = service
.getAll$()
// If we subscribe to the $getAll() Observable BEFORE setting a custom status Observable
// for a given plugin ('a' in this test), then the first emission will happen
// right after core$ services Observable emits
.pipe(skip(1))
.subscribe((pluginStatuses) => statusUpdates.push(pluginStatuses));

service.set('a', of({ level: ServiceStatusLevels.degraded, summary: 'a degraded' }));
Expand All @@ -261,6 +265,8 @@ describe('PluginStatusService', () => {
const statusUpdates: Array<Record<PluginName, ServiceStatus>> = [];
const subscription = service
.getAll$()
// the first emission happens right after core services emit (see explanation above)
.pipe(skip(1))
.subscribe((pluginStatuses) => statusUpdates.push(pluginStatuses));

const aStatus$ = new BehaviorSubject<ServiceStatus>({
Expand All @@ -280,19 +286,21 @@ describe('PluginStatusService', () => {
});

it('emits an unavailable status if first emission times out, then continues future emissions', async () => {
jest.useFakeTimers();
const service = new PluginsStatusService({
core$: coreAllAvailable$,
pluginDependencies: new Map([
['a', []],
['b', ['a']],
]),
});
const service = new PluginsStatusService(
{
core$: coreAllAvailable$,
pluginDependencies: new Map([
['a', []],
['b', ['a']],
]),
},
10 // set a small timeout so that the registered status Observable for 'a' times out quickly
);

const pluginA$ = new ReplaySubject<ServiceStatus>(1);
service.set('a', pluginA$);
const firstEmission = service.getAll$().pipe(first()).toPromise();
jest.runAllTimers();
// the first emission happens right after core$ services emit
const firstEmission = service.getAll$().pipe(skip(1), first()).toPromise();

expect(await firstEmission).toEqual({
a: { level: ServiceStatusLevels.unavailable, summary: 'Status check timed out after 30s' },
Expand All @@ -308,16 +316,16 @@ describe('PluginStatusService', () => {

pluginA$.next({ level: ServiceStatusLevels.available, summary: 'a available' });
const secondEmission = service.getAll$().pipe(first()).toPromise();
jest.runAllTimers();
expect(await secondEmission).toEqual({
a: { level: ServiceStatusLevels.available, summary: 'a available' },
b: { level: ServiceStatusLevels.available, summary: 'All dependencies are available' },
});
jest.useRealTimers();
});
});

describe('getDependenciesStatus$', () => {
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

it('only includes dependencies of specified plugin', async () => {
const service = new PluginsStatusService({
core$: coreAllAvailable$,
Expand Down Expand Up @@ -357,7 +365,7 @@ describe('PluginStatusService', () => {

it('debounces plugins custom status registration', async () => {
const service = new PluginsStatusService({
core$: coreAllAvailable$,
core$: coreOneCriticalOneDegraded$,
pluginDependencies,
});
const available: ServiceStatus = {
Expand All @@ -375,8 +383,6 @@ describe('PluginStatusService', () => {

expect(statusUpdates).toStrictEqual([]);

const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

// Waiting for the debounce timeout should cut a new update
await delay(25);
subscription.unsubscribe();
Expand Down Expand Up @@ -404,7 +410,6 @@ describe('PluginStatusService', () => {
const subscription = service
.getDependenciesStatus$('b')
.subscribe((status) => statusUpdates.push(status));
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

pluginA$.next(degraded);
pluginA$.next(available);
Expand Down
Loading

0 comments on commit e097e62

Please sign in to comment.