Skip to content

Commit

Permalink
Adds new contextual attributes to Infrastructure - Inventory Rule (#1…
Browse files Browse the repository at this point in the history
…40598)

* adding context for nodetype Hosts for Inventory rule

* removing unused import

* adding more action variables, fixing failing tests

* minor changes

* refactoring

* removing extra fields being added with context

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* fixing failing tests

* refactoring

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* adding component template ref

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'

* changed description

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
benakansara and kibanamachine authored Oct 12, 2022
1 parent 702827b commit 4f3558a
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 5 deletions.
42 changes: 42 additions & 0 deletions x-pack/plugins/infra/server/lib/alerting/common/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,3 +214,45 @@ export const viewInAppUrlActionVariableDescription = i18n.translate(
'Link to the view or feature within Elastic that can be used to investigate the alert and its context further',
}
);

export const cloudActionVariableDescription = i18n.translate(
'xpack.infra.metrics.alerting.cloudActionVariableDescription',
{
defaultMessage: 'The cloud object defined by ECS if available in the source.',
}
);

export const hostActionVariableDescription = i18n.translate(
'xpack.infra.metrics.alerting.hostActionVariableDescription',
{
defaultMessage: 'The host object defined by ECS if available in the source.',
}
);

export const containerActionVariableDescription = i18n.translate(
'xpack.infra.metrics.alerting.containerActionVariableDescription',
{
defaultMessage: 'The container object defined by ECS if available in the source.',
}
);

export const orchestratorActionVariableDescription = i18n.translate(
'xpack.infra.metrics.alerting.orchestratorActionVariableDescription',
{
defaultMessage: 'The orchestrator object defined by ECS if available in the source.',
}
);

export const labelsActionVariableDescription = i18n.translate(
'xpack.infra.metrics.alerting.labelsActionVariableDescription',
{
defaultMessage: 'List of labels associated with the entity where this alert triggered.',
}
);

export const tagsActionVariableDescription = i18n.translate(
'xpack.infra.metrics.alerting.tagsActionVariableDescription',
{
defaultMessage: 'List of tags associated with the entity where this alert triggered.',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export type ConditionResult = InventoryMetricConditions & {
currentValue: number;
isNoData: boolean;
isError: boolean;
context: [x: string];
};

export const evaluateCondition = async ({
Expand Down Expand Up @@ -82,6 +83,14 @@ export const evaluateCondition = async ({
isNoData: value === null,
isError: value === undefined,
currentValue: value.value,
context: {
cloud: value.cloud,
host: value.host,
container: value.container,
orchestrator: value.orchestrator,
labels: value.labels,
tags: value.tags,
},
};
}) as unknown; // Typescript doesn't seem to know what `throw` is doing

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type InventoryMetricThresholdAlert = Alert<
type InventoryMetricThresholdAlertFactory = (
id: string,
reason: string,
additionalContext?: [x: string] | null,
threshold?: number | undefined,
value?: number | undefined
) => InventoryMetricThresholdAlert;
Expand All @@ -66,12 +67,13 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions');
const logger = createScopedLogger(libs.logger, 'inventoryRule', { alertId, executionId });
const { alertWithLifecycle, savedObjectsClient, getAlertStartedDate } = services;
const alertFactory: InventoryMetricThresholdAlertFactory = (id, reason) =>
const alertFactory: InventoryMetricThresholdAlertFactory = (id, reason, additionalContext) =>
alertWithLifecycle({
id,
fields: {
[ALERT_REASON]: reason,
[ALERT_RULE_PARAMETERS]: params as any, // the type assumes the object is already flattened when writing the same way as when reading https://github.com/elastic/kibana/blob/main/x-pack/plugins/rule_registry/common/field_map/runtime_type_from_fieldmap.ts#L60
...additionalContext,
},
});

Expand Down Expand Up @@ -184,7 +186,9 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
const actionGroupId =
nextState === AlertStates.WARNING ? WARNING_ACTIONS.id : FIRED_ACTIONS.id;

const alert = alertFactory(group, reason);
const additionalContext = results && results.length > 0 ? results[0][group].context : null;

const alert = alertFactory(group, reason, additionalContext);
const indexedStartedDate = getAlertStartedDate(group) ?? startedAt.toISOString();
const viewInAppUrl = getViewInAppUrlInventory(
criteria,
Expand All @@ -193,6 +197,7 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
libs.basePath
);
scheduledActionsCount++;

const context = {
group,
alertState: stateToAlertMessage[nextState],
Expand All @@ -204,6 +209,7 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
),
threshold: mapToConditionsLookup(criteria, (c) => c.threshold),
metric: mapToConditionsLookup(criteria, (c) => c.metric),
...additionalContext,
};
alert.scheduleActions(actionGroupId, context);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,18 @@ export const createRequest = (
const metricAggregations = createMetricAggregations(timerange, nodeType, metric, customMetric);
const bucketSelector = createBucketSelector(metric, condition, customMetric);

const additionalContextAgg = {
additionalContext: {
top_hits: {
size: 1,
_source: {
includes: ['host.*', 'labels.*', 'tags', 'cloud.*', 'orchestrator.*', 'container.*'],
excludes: ['host.cpu.*', 'host.disk.*', 'host.network.*'],
},
},
},
};

const request: ESSearchRequest = {
allow_no_indices: true,
ignore_unavailable: true,
Expand All @@ -65,7 +77,7 @@ export const createRequest = (
aggs: {
nodes: {
composite,
aggs: { ...metricAggregations, ...bucketSelector },
aggs: { ...metricAggregations, ...bucketSelector, ...additionalContextAgg },
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
* 2.0.
*/

import { AggregationsAggregate, SearchResponse } from '@elastic/elasticsearch/lib/api/types';
import { ElasticsearchClient } from '@kbn/core/server';
import type { Logger } from '@kbn/logging';
import { EcsFieldsResponse } from '@kbn/rule-registry-plugin/common/search_strategy';
import { InventoryMetricConditions } from '../../../../../common/alerting/metrics';
import { InfraTimerangeInput, SnapshotCustomMetricInput } from '../../../../../common/http_api';
import {
Expand All @@ -20,13 +22,28 @@ import { createRequest } from './create_request';
interface BucketKey {
node: string;
}
type Response = Record<string, { value: number | null; warn: boolean; trigger: boolean }>;

interface AdditionalContext {
[x: string]: any;
}

type Response = Record<
string,
{
value: number | null;
warn: boolean;
trigger: boolean;
} & AdditionalContext
>;

type Metric = Record<string, { value: number | null }>;

interface Bucket {
key: BucketKey;
doc_count: number;
shouldWarn: { value: number };
shouldTrigger: { value: number };
additionalContext: SearchResponse<EcsFieldsResponse, Record<string, AggregationsAggregate>>;
}
type NodeBucket = Bucket & Metric;
interface ResponseAggregations {
Expand Down Expand Up @@ -56,10 +73,15 @@ export const getData = async (
const nextAfterKey = nodes.after_key;
for (const bucket of nodes.buckets) {
const metricId = customMetric && customMetric.field ? customMetric.id : metric;
const bucketHits = bucket.additionalContext?.hits?.hits;
const additionalContextSource =
bucketHits && bucketHits.length > 0 ? bucketHits[0]._source : null;

previous[bucket.key.node] = {
value: bucket?.[metricId]?.value ?? null,
warn: bucket?.shouldWarn.value > 0 ?? false,
trigger: bucket?.shouldTrigger.value > 0 ?? false,
...additionalContextSource,
};
}
if (nextAfterKey) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,15 @@ import {
import { InfraBackendLibs } from '../../infra_types';
import {
alertStateActionVariableDescription,
cloudActionVariableDescription,
containerActionVariableDescription,
groupActionVariableDescription,
hostActionVariableDescription,
labelsActionVariableDescription,
metricActionVariableDescription,
orchestratorActionVariableDescription,
reasonActionVariableDescription,
tagsActionVariableDescription,
thresholdActionVariableDescription,
timestampActionVariableDescription,
valueActionVariableDescription,
Expand Down Expand Up @@ -102,6 +108,12 @@ export async function registerMetricInventoryThresholdRuleType(
{ name: 'metric', description: metricActionVariableDescription },
{ name: 'threshold', description: thresholdActionVariableDescription },
{ name: 'viewInAppUrl', description: viewInAppUrlActionVariableDescription },
{ name: 'cloud', description: cloudActionVariableDescription },
{ name: 'host', description: hostActionVariableDescription },
{ name: 'container', description: containerActionVariableDescription },
{ name: 'orchestrator', description: orchestratorActionVariableDescription },
{ name: 'labels', description: labelsActionVariableDescription },
{ name: 'tags', description: tagsActionVariableDescription },
],
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { mappingFromFieldMap } from '@kbn/rule-registry-plugin/common/mapping_fr
import { experimentalRuleFieldMap } from '@kbn/rule-registry-plugin/common/assets/field_maps/experimental_rule_field_map';

import { Dataset, RuleRegistryPluginSetupContract } from '@kbn/rule-registry-plugin/server';
import { ECS_COMPONENT_TEMPLATE_NAME } from '@kbn/rule-registry-plugin/common/assets';
import type { InfraFeatureId } from '../../../common/constants';
import { RuleRegistrationContext, RulesServiceStartDeps } from './types';

Expand All @@ -30,7 +31,7 @@ export const createRuleDataClient = ({
feature: ownerFeatureId,
registrationContext,
dataset: Dataset.alerts,
componentTemplateRefs: [],
componentTemplateRefs: [ECS_COMPONENT_TEMPLATE_NAME],
componentTemplates: [
{
name: 'mappings',
Expand Down
Loading

0 comments on commit 4f3558a

Please sign in to comment.