Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.x] [Chore] Isolate CCS check (#195988) #196869

Merged
merged 1 commit into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/kbn-es-query/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export {
getDataViewFieldSubtypeNested,
isDataViewFieldSubtypeMulti,
isDataViewFieldSubtypeNested,
isCCSRemoteIndexName,
} from './src/utils';

export type { ExecutionContextSearch } from './src/expressions/types';
34 changes: 34 additions & 0 deletions packages/kbn-es-query/src/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { isCCSRemoteIndexName } from './utils';

describe('util tests', () => {
describe('isCCSRemoteIndexName', () => {
it('should not validate empty string', () => {
expect(isCCSRemoteIndexName('')).toBe(false);
});

it('should not validate date math expression', () => {
expect(isCCSRemoteIndexName('<logstash-{now/d-2d}>')).toBe(false);
});

it('should not validate date math expression with negation', () => {
expect(isCCSRemoteIndexName('-<logstash-{now/d-2d}>')).toBe(false);
});

it('should not validate invalid prefix', () => {
expect(isCCSRemoteIndexName(':logstash-{now/d-2d}')).toBe(false);
});

it('should validate CCS pattern', () => {
expect(isCCSRemoteIndexName('*:logstash-{now/d-2d}')).toBe(true);
});
});
});
19 changes: 19 additions & 0 deletions packages/kbn-es-query/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,22 @@ export function isDataViewFieldSubtypeMulti(field: HasSubtype) {
export function getDataViewFieldSubtypeMulti(field: HasSubtype) {
return isDataViewFieldSubtypeMulti(field) ? (field.subType as IFieldSubTypeMulti) : undefined;
}

/**
* Check whether the index expression represents a remote index (CCS) or not.
* The index name is assumed to be individual index (no commas) but can contain `-`, wildcards,
* datemath, remote cluster name and any other syntax permissible in index expression component.
*
* 2024/10/11 Implementation taken from https://github.com/smalyshev/elasticsearch/blob/main/server/src/main/java/org/elasticsearch/transport/RemoteClusterAware.java
*
* @param indexExpression
*/
export function isCCSRemoteIndexName(indexExpression: string): boolean {
if (indexExpression === '' || indexExpression[0] === '<' || indexExpression.startsWith('-<')) {
// This is date math, but even if it is not, the remote can't start with '<'.
// Thus, whatever it is, this is definitely not a remote index.
return false;
}
// Note remote index name also can not start with ':'
return indexExpression.indexOf(':') > 0;
}
3 changes: 2 additions & 1 deletion x-pack/plugins/ml/public/application/util/index_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import type { SavedSearch, SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public';
import { isCCSRemoteIndexName } from '@kbn/es-query';
import type { Query, Filter } from '@kbn/es-query';
import type { DataView, DataViewField, DataViewsContract } from '@kbn/data-views-plugin/common';

Expand Down Expand Up @@ -51,7 +52,7 @@ export function getQueryFromSavedSearchObject(savedSearch: SavedSearch) {
* which means it is cross-cluster
*/
export function isCcsIndexPattern(indexPattern: string) {
return indexPattern.includes(':');
return isCCSRemoteIndexName(indexPattern);
}

export function findMessageField(
Expand Down
9 changes: 4 additions & 5 deletions x-pack/plugins/monitoring/common/ccs_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import { isCCSRemoteIndexName } from '@kbn/es-query';
import type { MonitoringConfig } from '../server/config';

/**
Expand Down Expand Up @@ -67,13 +68,11 @@ export function prefixIndexPatternWithCcs(
* @return {String} {@code null} if none. Otherwise the cluster prefix.
*/
export function parseCrossClusterPrefix(indexName: string): string | null {
const colonIndex = indexName.indexOf(':');

if (colonIndex === -1) {
const isCcs = isCCSRemoteIndexName(indexName);
if (!isCcs) {
return null;
}

// if we found a : in the index name, then cross-cluster search (CCS) was used to find the cluster
// and we _should_ use it when we search explicitly for this cluster (to avoid inefficiently checking other monitoring _clusters_)
const colonIndex = indexName.indexOf(':');
return indexName.substr(0, colonIndex);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { ElasticsearchClient } from '@kbn/core/server';
import { get } from 'lodash';
import { isCCSRemoteIndexName } from '@kbn/es-query';
import { CCS_REMOTE_PATTERN } from '../../../common/constants';
import { CCRReadExceptionsStats } from '../../../common/types/alerts';
import { getIndexPatterns, getElasticsearchDataset } from '../cluster/get_index_patterns';
Expand Down Expand Up @@ -173,7 +174,7 @@ export async function fetchCCRReadExceptions(
shardId,
leaderIndex,
lastReadException,
ccs: monitoringIndexName.includes(':') ? monitoringIndexName.split(':')[0] : null,
ccs: isCCSRemoteIndexName(monitoringIndexName) ? monitoringIndexName.split(':')[0] : null,
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/
import { ElasticsearchClient } from '@kbn/core/server';
import { isCCSRemoteIndexName } from '@kbn/es-query';
import { AlertCluster, AlertClusterHealth } from '../../../common/types/alerts';
import { ElasticsearchSource } from '../../../common/types/es';
import { createDatasetFilter } from './create_dataset_query_filter';
Expand Down Expand Up @@ -87,7 +88,7 @@ export async function fetchClusterHealth(
health:
hit._source!.cluster_state?.status || hit._source!.elasticsearch?.cluster?.stats?.status,
clusterUuid: hit._source!.cluster_uuid || hit._source!.elasticsearch?.cluster?.id,
ccs: hit._index.includes(':') ? hit._index.split(':')[0] : undefined,
ccs: isCCSRemoteIndexName(hit._index) ? hit._index.split(':')[0] : undefined,
} as AlertClusterHealth;
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import React from 'react';
import { EuiFlexGroup, EuiCallOut, EuiDescriptionList, EuiSpacer } from '@elastic/eui';
import { isCCSRemoteIndexName } from '@kbn/es-query';

import { APIReturnType } from '../../../../services/rest/create_call_apm_api';
import { ApmIntegrationPackageStatus } from './apm_integration_package_status';
Expand Down Expand Up @@ -99,7 +100,7 @@ function PrivilegesCallout({ diagnosticsBundle }: { diagnosticsBundle: Diagnosti
}

export function getIsCrossCluster(diagnosticsBundle?: DiagnosticsBundle) {
return Object.values(diagnosticsBundle?.apmIndices ?? {}).some((indicies) =>
indicies.includes(':')
);
return Object.values(diagnosticsBundle?.apmIndices ?? {}).some((indicies) => {
return isCCSRemoteIndexName(indicies);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import { isCCSRemoteIndexName } from '@kbn/es-query';
import { ERROR_CORRELATION_THRESHOLD } from '../../../../common/correlations/constants';
import type { FailedTransactionsCorrelation } from '../../../../common/correlations/failed_transactions_correlations/types';

Expand Down Expand Up @@ -104,7 +105,7 @@ export const fetchPValues = async ({

const index = apmEventClient.indices[eventType as keyof typeof apmEventClient.indices];

const ccsWarning = rejected.length > 0 && index.includes(':');
const ccsWarning = rejected.length > 0 && isCCSRemoteIndexName(index);

return { failedTransactionsCorrelations, ccsWarning, fallbackResult };
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { range } from 'lodash';

import { termQuery } from '@kbn/observability-plugin/server';
import { isCCSRemoteIndexName } from '@kbn/es-query';
import type { LatencyCorrelation } from '../../../../common/correlations/latency_correlations/types';
import type {
CommonCorrelationsQueryParams,
Expand Down Expand Up @@ -171,7 +172,7 @@ export const fetchSignificantCorrelations = async ({

const index = apmEventClient.indices[eventType as keyof typeof apmEventClient.indices];

const ccsWarning = rejected.length > 0 && index.includes(':');
const ccsWarning = rejected.length > 0 && isCCSRemoteIndexName(index);

return {
latencyCorrelations,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
* 2.0.
*/

import { isCCSRemoteIndexName } from '@kbn/es-query';
import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client';
import { getApmIndicesCombined } from './indices_stats_helpers';

export function isCrossClusterSearch(apmEventClient: APMEventClient) {
// Check if a remote cluster is set in APM indices
return getApmIndicesCombined(apmEventClient).includes(':');
const index = getApmIndicesCombined(apmEventClient);
return isCCSRemoteIndexName(index);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { chunk, get, invert, isEmpty, partition } from 'lodash';
import moment from 'moment';

import dateMath from '@kbn/datemath';
import { isCCSRemoteIndexName } from '@kbn/es-query';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { TransportResult } from '@elastic/elasticsearch';
import { ALERT_UUID, ALERT_RULE_UUID, ALERT_RULE_PARAMETERS } from '@kbn/rule-data-utils';
Expand Down Expand Up @@ -82,7 +83,9 @@ export const hasReadIndexPrivileges = async (args: {
const indexNames = Object.keys(privileges.index);
const filteredIndexNames = isCcsPermissionWarningEnabled
? indexNames
: indexNames.filter((indexName) => !indexName.includes(':')); // Cross cluster indices uniquely contain `:` in their name
: indexNames.filter((indexName) => {
return !isCCSRemoteIndexName(indexName);
});

const [, indexesWithNoReadPrivileges] = partition(
filteredIndexNames,
Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/transform/public/app/hooks/use_index_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
} from '@kbn/ml-data-grid';
import type { TimeRange as TimeRangeMs } from '@kbn/ml-date-picker';

import { isCCSRemoteIndexName } from '@kbn/es-query';
import {
hasKeywordDuplicate,
isKeywordDuplicate,
Expand Down Expand Up @@ -176,7 +177,7 @@ export const useIndexData = (options: UseIndexDataOptions): UseIndexDataReturnTy
setErrorMessage(getErrorMessage(dataGridDataError));
setStatus(INDEX_STATUS.ERROR);
} else if (!dataGridDataIsLoading && !dataGridDataIsError && dataGridData !== undefined) {
const isCrossClusterSearch = indexPattern.includes(':');
const isCrossClusterSearch = isCCSRemoteIndexName(indexPattern);
const isMissingFields = dataGridData.hits.hits.every((d) => typeof d.fields === 'undefined');

const docs = dataGridData.hits.hits.map((d) => getProcessedFields(d.fields ?? {}));
Expand Down