Skip to content

Commit

Permalink
[8.x] [APM] Use excluded data tiers setting (#192373) (#193822)
Browse files Browse the repository at this point in the history
# Backport

This will backport the following commits from `main` to `8.x`:
- [[APM] Use excluded data tiers setting
(#192373)](#192373)

<!--- Backport version: 8.9.8 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Carlos
Crespo","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-09-23T15:59:08Z","message":"[APM]
Use excluded data tiers setting (#192373)\n\ncloses
[#190559](https://github.com/elastic/kibana/issues/190559)\r\n\r\n##
Summary\r\n\r\nThis PR updates the ES clients in APM to respect the
excluded tier\r\nconfiguration. When this config is set, the ES clients
will\r\nautomatically add a filter to exclude the specified tiers from
queries.\r\n\r\n<img width=\"600\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/9b0de76d-242c-4343-bc30-d5c787316f59\">\r\n\r\nAll
queries in APM should have the `_tier` filter
(via\r\n`get_apm_events_client`)\r\n<img width=\"600\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/c525602f-f239-4be8-99c4-65d617962656\">\r\n\r\nThis
change also affects alerting (via `alerting_es_client`)\r\n<img
width=\"600\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/750df4d7-5b49-4de5-9294-7afedf11d7e5\">\r\n\r\nAnd
it impacts the alerts column (via `get_apm_alert_client`)\r\n<img
width=\"600\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/44bd9129-1e72-4a3a-af32-d42a9cd9164d\">\r\n\r\n###
What won't automatically add a filter for `_tier`\r\n\r\n-
Embeddables\r\n- ML queries\r\n\r\n### How to test\r\n- Set the config
in Advanced Settings to exclude `data_frozen` and\r\n`data_cold`
(optional)\r\n- Navigate to APM and check the query `Inspect` to see if
the filter is\r\npresent.\r\n- Click through APM to confirm things still
work.\r\n- Create one of each type of APM alerts\r\n- Without the config
set, queries should not include the `_tier`
filter`\r\n\r\n---------\r\n\r\nCo-authored-by: Elastic Machine
<[email protected]>","sha":"ee5ef8166b74041f359862f3f22e0eb491f1443c","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","apm","backport:prev-major","ci:project-deploy-observability","Team:obs-ux-infra_services","Team:obs-ux-management","v8.16.0"],"number":192373,"url":"https://github.com/elastic/kibana/pull/192373","mergeCommit":{"message":"[APM]
Use excluded data tiers setting (#192373)\n\ncloses
[#190559](https://github.com/elastic/kibana/issues/190559)\r\n\r\n##
Summary\r\n\r\nThis PR updates the ES clients in APM to respect the
excluded tier\r\nconfiguration. When this config is set, the ES clients
will\r\nautomatically add a filter to exclude the specified tiers from
queries.\r\n\r\n<img width=\"600\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/9b0de76d-242c-4343-bc30-d5c787316f59\">\r\n\r\nAll
queries in APM should have the `_tier` filter
(via\r\n`get_apm_events_client`)\r\n<img width=\"600\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/c525602f-f239-4be8-99c4-65d617962656\">\r\n\r\nThis
change also affects alerting (via `alerting_es_client`)\r\n<img
width=\"600\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/750df4d7-5b49-4de5-9294-7afedf11d7e5\">\r\n\r\nAnd
it impacts the alerts column (via `get_apm_alert_client`)\r\n<img
width=\"600\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/44bd9129-1e72-4a3a-af32-d42a9cd9164d\">\r\n\r\n###
What won't automatically add a filter for `_tier`\r\n\r\n-
Embeddables\r\n- ML queries\r\n\r\n### How to test\r\n- Set the config
in Advanced Settings to exclude `data_frozen` and\r\n`data_cold`
(optional)\r\n- Navigate to APM and check the query `Inspect` to see if
the filter is\r\npresent.\r\n- Click through APM to confirm things still
work.\r\n- Create one of each type of APM alerts\r\n- Without the config
set, queries should not include the `_tier`
filter`\r\n\r\n---------\r\n\r\nCo-authored-by: Elastic Machine
<[email protected]>","sha":"ee5ef8166b74041f359862f3f22e0eb491f1443c"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/192373","number":192373,"mergeCommit":{"message":"[APM]
Use excluded data tiers setting (#192373)\n\ncloses
[#190559](https://github.com/elastic/kibana/issues/190559)\r\n\r\n##
Summary\r\n\r\nThis PR updates the ES clients in APM to respect the
excluded tier\r\nconfiguration. When this config is set, the ES clients
will\r\nautomatically add a filter to exclude the specified tiers from
queries.\r\n\r\n<img width=\"600\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/9b0de76d-242c-4343-bc30-d5c787316f59\">\r\n\r\nAll
queries in APM should have the `_tier` filter
(via\r\n`get_apm_events_client`)\r\n<img width=\"600\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/c525602f-f239-4be8-99c4-65d617962656\">\r\n\r\nThis
change also affects alerting (via `alerting_es_client`)\r\n<img
width=\"600\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/750df4d7-5b49-4de5-9294-7afedf11d7e5\">\r\n\r\nAnd
it impacts the alerts column (via `get_apm_alert_client`)\r\n<img
width=\"600\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/44bd9129-1e72-4a3a-af32-d42a9cd9164d\">\r\n\r\n###
What won't automatically add a filter for `_tier`\r\n\r\n-
Embeddables\r\n- ML queries\r\n\r\n### How to test\r\n- Set the config
in Advanced Settings to exclude `data_frozen` and\r\n`data_cold`
(optional)\r\n- Navigate to APM and check the query `Inspect` to see if
the filter is\r\npresent.\r\n- Click through APM to confirm things still
work.\r\n- Create one of each type of APM alerts\r\n- Without the config
set, queries should not include the `_tier`
filter`\r\n\r\n---------\r\n\r\nCo-authored-by: Elastic Machine
<[email protected]>","sha":"ee5ef8166b74041f359862f3f22e0eb491f1443c"}},{"branch":"8.x","label":"v8.16.0","labelRegex":"^v8.16.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Carlos Crespo <[email protected]>
  • Loading branch information
cauemarcondes and crespocarlos authored Sep 24, 2024
1 parent 04b65f2 commit 51e6c39
Show file tree
Hide file tree
Showing 25 changed files with 612 additions and 117 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,8 @@
* 2.0.
*/
import type { estypes } from '@elastic/elasticsearch';
import { excludeTiersQuery } from './exclude_tiers_query';

export function excludeFrozenQuery(): estypes.QueryDslQueryContainer[] {
return [
{
bool: {
must_not: [
{
term: {
_tier: 'data_frozen',
},
},
],
},
},
];
return excludeTiersQuery(['data_frozen']);
}
Original file line number Diff line number Diff line change
@@ -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.
*/

import type { estypes } from '@elastic/elasticsearch';

export function excludeTiersQuery(
excludedDataTiers: Array<'data_frozen' | 'data_cold' | 'data_warm' | 'data_hot'>
): estypes.QueryDslQueryContainer[] {
return [
{
bool: {
must_not: [
{
terms: {
_tier: excludedDataTiers,
},
},
],
},
},
];
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,13 @@
* 2.0.
*/

import {
IndexLifecyclePhaseSelectOption,
indexLifeCyclePhaseToDataTier,
} from '@kbn/observability-shared-plugin/common';
import * as t from 'io-ts';

export enum IndexLifecyclePhaseSelectOption {
All = 'all',
Hot = 'hot',
Warm = 'warm',
Cold = 'cold',
Frozen = 'frozen',
}

export const indexLifeCyclePhaseToDataTier = {
[IndexLifecyclePhaseSelectOption.Hot]: 'data_hot',
[IndexLifecyclePhaseSelectOption.Warm]: 'data_warm',
[IndexLifecyclePhaseSelectOption.Cold]: 'data_cold',
[IndexLifecyclePhaseSelectOption.Frozen]: 'data_frozen',
};

export { IndexLifecyclePhaseSelectOption, indexLifeCyclePhaseToDataTier };
export const indexLifecyclePhaseRt = t.type({
indexLifecyclePhase: t.union([
t.literal(IndexLifecyclePhaseSelectOption.All),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* 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 ApmAlertsRequiredParams, getApmAlertsClient } from './get_apm_alerts_client';
import type {
IScopedClusterClient,
IUiSettingsClient,
KibanaRequest,
SavedObjectsClientContract,
} from '@kbn/core/server';
import { AlertsClient, RuleRegistryPluginStartContract } from '@kbn/rule-registry-plugin/server';

describe('get_apm_alerts_client', () => {
let ruleRegistryMock: jest.Mocked<RuleRegistryPluginStartContract>;
let alertClient: jest.Mocked<AlertsClient>;
let uiSettingsClientMock: jest.Mocked<IUiSettingsClient>;

const params: ApmAlertsRequiredParams = {
size: 10,
track_total_hits: true,
query: {
match: { field: 'value' },
},
};

beforeEach(async () => {
uiSettingsClientMock = {
get: jest.fn().mockResolvedValue(undefined),
} as unknown as jest.Mocked<IUiSettingsClient>;

alertClient = {
find: jest.fn().mockResolvedValue({}),
getAuthorizedAlertsIndices: jest.fn().mockResolvedValue(['apm']),
} as unknown as jest.Mocked<AlertsClient>;

ruleRegistryMock = {
getRacClientWithRequest: jest.fn().mockResolvedValue(alertClient),
alerting: jest.fn(),
} as unknown as jest.Mocked<RuleRegistryPluginStartContract>;
});

afterEach(() => {
jest.resetAllMocks();
});

// Helper function to create the APM alerts client
const createApmAlertsClient = async () => {
return await getApmAlertsClient({
context: {
core: Promise.resolve({
uiSettings: { client: uiSettingsClientMock },
elasticsearch: { client: {} as IScopedClusterClient },
savedObjects: { client: {} as SavedObjectsClientContract },
}),
} as any,
plugins: {
ruleRegistry: {
start: jest.fn().mockResolvedValue(ruleRegistryMock),
setup: {} as any,
},
} as any,
request: {} as KibanaRequest,
});
};

it('should call search', async () => {
const apmAlertsClient = await createApmAlertsClient();

await apmAlertsClient.search(params);

const searchParams = alertClient.find.mock.calls[0][0] as ApmAlertsRequiredParams;
expect(searchParams.query).toEqual({ match: { field: 'value' } });
});

it('should call search with filters containing excluded data tiers', async () => {
const excludedDataTiers = ['data_warm', 'data_cold'];
uiSettingsClientMock.get.mockResolvedValue(excludedDataTiers);

const apmAlertsClient = await createApmAlertsClient();

await apmAlertsClient.search(params);

const searchParams = alertClient.find.mock.calls[0][0] as ApmAlertsRequiredParams;
expect(searchParams.query?.bool).toEqual({
must: [
{ match: { field: 'value' } },
{ bool: { must_not: [{ terms: { _tier: ['data_warm', 'data_cold'] } }] } },
],
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,27 @@
import { isEmpty } from 'lodash';
import { ESSearchRequest, InferSearchResponseOf } from '@kbn/es-types';
import { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common';
import { DataTier } from '@kbn/observability-shared-plugin/common';
import { searchExcludedDataTiers } from '@kbn/observability-plugin/common/ui_settings_keys';
import { estypes } from '@elastic/elasticsearch';
import { getDataTierFilterCombined } from '@kbn/apm-data-access-plugin/server/utils';
import type { MinimalAPMRouteHandlerResources } from '../../routes/apm_routes/register_apm_server_routes';

export type ApmAlertsClient = Awaited<ReturnType<typeof getApmAlertsClient>>;

export type ApmAlertsRequiredParams = ESSearchRequest & {
size: number;
track_total_hits: boolean | number;
query?: estypes.QueryDslQueryContainer;
};

export async function getApmAlertsClient({
context,
plugins,
request,
}: Pick<MinimalAPMRouteHandlerResources, 'plugins' | 'request'>) {
}: Pick<MinimalAPMRouteHandlerResources, 'context' | 'plugins' | 'request'>) {
const coreContext = await context.core;

const ruleRegistryPluginStart = await plugins.ruleRegistry.start();
const alertsClient = await ruleRegistryPluginStart.getRacClientWithRequest(request);
const apmAlertsIndices = await alertsClient.getAuthorizedAlertsIndices(['apm']);
Expand All @@ -24,17 +37,20 @@ export async function getApmAlertsClient({
throw Error('No alert indices exist for "apm"');
}

type RequiredParams = ESSearchRequest & {
size: number;
track_total_hits: boolean | number;
};
const excludedDataTiers = await coreContext.uiSettings.client.get<DataTier[]>(
searchExcludedDataTiers
);

return {
search<TParams extends RequiredParams>(
search<TParams extends ApmAlertsRequiredParams>(
searchParams: TParams
): Promise<InferSearchResponseOf<ParsedTechnicalFields, TParams>> {
return alertsClient.find({
...searchParams,
query: getDataTierFilterCombined({
filter: searchParams.query,
excludedDataTiers,
}),
index: apmAlertsIndices.join(','),
}) as Promise<any>;
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
*/

import { UI_SETTINGS } from '@kbn/data-plugin/common';
import { DataTier } from '@kbn/observability-shared-plugin/common';
import { searchExcludedDataTiers } from '@kbn/observability-plugin/common/ui_settings_keys';
import { APMEventClient } from './create_es_client/create_apm_event_client';
import { withApmSpan } from '../../utils/with_apm_span';
import { MinimalAPMRouteHandlerResources } from '../../routes/apm_routes/register_apm_server_routes';
Expand All @@ -22,11 +24,18 @@ export async function getApmEventClient({
>): Promise<APMEventClient> {
return withApmSpan('get_apm_event_client', async () => {
const coreContext = await context.core;
const [indices, includeFrozen] = await Promise.all([
const [indices, uiSettings] = await Promise.all([
getApmIndices(),
withApmSpan('get_ui_settings', () =>
coreContext.uiSettings.client.get<boolean>(UI_SETTINGS.SEARCH_INCLUDE_FROZEN)
),
withApmSpan('get_ui_settings', async () => {
const includeFrozen = await coreContext.uiSettings.client.get<boolean>(
UI_SETTINGS.SEARCH_INCLUDE_FROZEN
);
const excludedDataTiers = await coreContext.uiSettings.client.get<DataTier[]>(
searchExcludedDataTiers
);

return { includeFrozen, excludedDataTiers };
}),
]);

return new APMEventClient({
Expand All @@ -35,7 +44,8 @@ export async function getApmEventClient({
request,
indices,
options: {
includeFrozen,
includeFrozen: uiSettings.includeFrozen,
excludedDataTiers: uiSettings.excludedDataTiers,
inspectableEsQueriesMap,
},
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* 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 APMEventESSearchRequestParams, alertingEsClient } from './alerting_es_client';
import type { RuleExecutorServices } from '@kbn/alerting-plugin/server';
import type { ElasticsearchClient, IUiSettingsClient } from '@kbn/core/server';
import type { ESSearchResponse } from '@kbn/es-types';

describe('alertingEsClient', () => {
let scopedClusterClientMock: jest.Mocked<{
asCurrentUser: jest.Mocked<ElasticsearchClient>;
}>;

let uiSettingsClientMock: jest.Mocked<IUiSettingsClient>;

const params = {
body: {
size: 10,
track_total_hits: true,
query: {
match: { field: 'value' },
},
},
};

const mockSearchResponse = {
hits: {
total: { value: 1, relation: 'eq' },
hits: [{ _source: {}, _index: '' }],
max_score: 1,
},
took: 1,
_shards: { total: 1, successful: 1, skipped: 0, failed: 0 },
timed_out: false,
} as unknown as ESSearchResponse<unknown, typeof params>;

beforeEach(() => {
scopedClusterClientMock = {
asCurrentUser: {
search: jest.fn().mockResolvedValue(mockSearchResponse),
} as unknown as jest.Mocked<ElasticsearchClient>,
};

uiSettingsClientMock = {
get: jest.fn().mockResolvedValue(undefined),
} as unknown as jest.Mocked<IUiSettingsClient>;
});

afterEach(() => {
jest.resetAllMocks();
});

// Helper function to perform the search
const performSearch = async (searchParams: APMEventESSearchRequestParams) => {
return await alertingEsClient({
scopedClusterClient: scopedClusterClientMock as unknown as RuleExecutorServices<
never,
never,
never
>['scopedClusterClient'],
uiSettingsClient: uiSettingsClientMock,
params: searchParams,
});
};

it('should call search with default params', async () => {
await performSearch(params);

const searchParams = scopedClusterClientMock.asCurrentUser.search.mock
.calls[0][0] as APMEventESSearchRequestParams;
expect(searchParams.body?.query).toEqual({ match: { field: 'value' } });
});

it('should call search with filters containing excluded data tiers', async () => {
const excludedDataTiers = ['data_warm', 'data_cold'];
uiSettingsClientMock.get.mockResolvedValue(excludedDataTiers);

await performSearch(params);

const searchParams = scopedClusterClientMock.asCurrentUser.search.mock
.calls[0][0] as APMEventESSearchRequestParams;
expect(searchParams.body?.query?.bool).toEqual({
must: [
{ match: { field: 'value' } },
{ bool: { must_not: [{ terms: { _tier: ['data_warm', 'data_cold'] } }] } },
],
});
});
});
Loading

0 comments on commit 51e6c39

Please sign in to comment.