Skip to content

Commit

Permalink
[Chore] Isolate CCS check (#195988)
Browse files Browse the repository at this point in the history
## Summary

Isolate CCS check in single location.

Closes
#193906 (comment)

### For maintainers

- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#_add_your_labels)

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
(cherry picked from commit f86d171)
  • Loading branch information
thomasneirynck committed Oct 18, 2024
1 parent f7a0fea commit debb649
Show file tree
Hide file tree
Showing 13 changed files with 81 additions and 16 deletions.
1 change: 1 addition & 0 deletions packages/kbn-es-query/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,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 @@ -35,3 +35,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 @@ -234,7 +235,7 @@ export const useIndexData = (
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

0 comments on commit debb649

Please sign in to comment.