[UA] Exclude critical, reindex enterprise search data stream deprecations (#211881)

## Summary

Close https://github.com/elastic/kibana/issues/211712

Related https://github.com/elastic/kibana/pull/211847
This commit is contained in:
Jean-Louis Leysens 2025-02-25 12:54:59 +01:00 committed by GitHub
parent 198ed68669
commit 4c42363a7a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 251 additions and 120 deletions

View file

@ -232,21 +232,63 @@
"details": "This data stream has backing indices that were created before Elasticsearch 8.0.0",
"resolve_during_rolling_upgrade": false,
"_meta": {
"backing_indices": {
"count": 52,
"need_upgrading": {
"count": 37,
"searchable_snapshot": {
"count": 23,
"fully_mounted": {
"count": 7
},
"partially_mounted": {
"count": 16
}
}
}
}
"indices_requiring_upgrade": [
".ds-some-backing-index-5-2024.11.07-000001"
],
"indices_requiring_upgrade_count": 1,
"total_backing_indices": 2,
"reindex_required": true
}
}
],
"logs-enterprise_search.default": [
{
"level": "critical",
"message": "Old data stream with a compatibility version < 8.0",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-9.0.html",
"details": "This data stream has backing indices that were created before Elasticsearch 8.0.0",
"resolve_during_rolling_upgrade": false,
"_meta": {
"indices_requiring_upgrade": [
".ds-some-backing-index-5-2024.11.07-000001"
],
"indices_requiring_upgrade_count": 1,
"total_backing_indices": 2,
"reindex_required": true
}
}
],
"logs-app_search.default": [
{
"level": "critical",
"message": "Old data stream with a compatibility version < 8.0",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-9.0.html",
"details": "This data stream has backing indices that were created before Elasticsearch 8.0.0",
"resolve_during_rolling_upgrade": false,
"_meta": {
"indices_requiring_upgrade": [
".ds-some-backing-index-5-2024.11.07-000001"
],
"indices_requiring_upgrade_count": 1,
"total_backing_indices": 2,
"reindex_required": true
}
}
],
"logs-workplace_search.default": [
{
"level": "critical",
"message": "Old data stream with a compatibility version < 8.0",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-9.0.html",
"details": "This data stream has backing indices that were created before Elasticsearch 8.0.0",
"resolve_during_rolling_upgrade": false,
"_meta": {
"indices_requiring_upgrade": [
".ds-some-backing-index-5-2024.11.07-000001"
],
"indices_requiring_upgrade_count": 1,
"total_backing_indices": 2,
"reindex_required": true
}
}
]

View file

@ -9,13 +9,7 @@ import { setPreEightEnterpriseSearchIndicesReadOnly } from './pre_eight_index_de
import { versionCheckHandlerWrapper } from '../es_version_precheck';
import { RouteDependencies } from '../../types';
export function registerEnterpriseSearchDeprecationRoutes({
config: { featureSet },
router,
lib: { handleEsError },
licensing,
log,
}: RouteDependencies) {
export function registerEnterpriseSearchDeprecationRoutes({ router }: RouteDependencies) {
router.post(
{
path: '/internal/enterprise_search/deprecations/set_enterprise_search_indices_read_only',

View file

@ -0,0 +1,12 @@
/*
* 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.
*/
export {
ENT_SEARCH_DATASTREAM_PATTERN,
ENT_SEARCH_DATASTREAM_PREFIXES,
ENT_SEARCH_INDEX_PREFIX,
} from './pre_eight_index_deprecator';

View file

@ -11,17 +11,14 @@ import {
getPreEightEnterpriseSearchIndices,
setPreEightEnterpriseSearchIndicesReadOnly,
} from './pre_eight_index_deprecator';
import type {
IndicesDataStream,
IndicesGetDataStreamResponse,
IndicesGetResponse,
IndicesIndexState,
} from '@elastic/elasticsearch/lib/api/types';
const testIndices = {
'non-ent-search-index': {
settings: {
index: {
version: {
created: '7.0.0',
},
},
},
},
'.ent-search-already_read_only': {
settings: {
index: {
@ -86,16 +83,30 @@ const testIndices = {
},
};
const additionalDatastreams = {
'logs-app_search.testdatastream': {
indices: [
{ index_name: 'non-existingindex' },
{ index_name: '.ent-search-with_same_data_stream' },
],
const testBackingIndex = {
'.ds-some-other-backing-index': {
settings: {
index: {
version: {
created: '7.0.0',
},
},
},
data_stream: 'logs-app_search.testdatastream',
},
};
const testIndicesWithoutDatastream = {
const additionalDatastreams: Record<string, IndicesDataStream> = {
'logs-app_search.testdatastream': {
name: 'logs-app_search.testdatastream',
indices: [
{ index_name: '.ds-some-other-backing-index', index_uuid: '1' },
{ index_name: '.ent-search-with_same_data_stream', index_uuid: '2' },
],
} as IndicesDataStream,
};
const testIndicesWithoutDatastream: Record<string, IndicesIndexState> = {
'.ent-search-already_read_only': {
settings: {
index: {
@ -129,58 +140,43 @@ const testIndicesWithoutDatastream = {
},
};
function getMockIndicesFxn(values: any) {
return jest.fn((params) => {
let prefix = params.index;
let isWildcard = false;
if (params.index.endsWith('*')) {
prefix = params.index.slice(0, -1);
isWildcard = true;
}
const ret: any = {};
function getMockIndicesFxn(values: Record<string, IndicesIndexState>) {
return () => {
const ret: IndicesGetResponse = {};
for (const [index, indexData] of Object.entries(values)) {
if (index === prefix || (isWildcard && index.startsWith(prefix))) {
ret[index] = indexData;
}
ret[index] = indexData;
}
return Promise.resolve(ret);
});
};
}
function getMockDatastreamsFxn(values: any) {
return jest.fn((params) => {
const prefixes = [];
const names = params.name.split(',');
for (const name of names) {
if (name.endsWith('*')) {
prefixes.push(name.slice(0, -1));
} else {
prefixes.push(name);
}
function getMockDatastreamsFxn(values: Record<string, IndicesDataStream>) {
return () => {
const ret: IndicesGetDataStreamResponse = { data_streams: [] };
for (const [, datastreamData] of Object.entries(values)) {
ret.data_streams.push(datastreamData);
}
const ret: any = {};
for (const [datastream, datastreamData] of Object.entries(values)) {
for (const prefix of prefixes) {
if (datastream.startsWith(prefix)) {
ret[datastream] = datastreamData;
break;
}
}
return Promise.resolve(ret);
}
});
return Promise.resolve(ret);
};
}
describe('getPreEightEnterpriseSearchIndices', () => {
const getIndicesMock = getMockIndicesFxn(testIndices);
const getDatastreamsMock = getMockDatastreamsFxn(additionalDatastreams);
const esClientMock = {
indices: {
get: getIndicesMock,
getDataStream: getDatastreamsMock,
},
} as unknown as ElasticsearchClient;
let esClientMock: ElasticsearchClient;
let getIndicesMock: jest.Mock;
let getDatastreamsMock: jest.Mock;
beforeEach(() => {
getIndicesMock = jest.fn();
getIndicesMock.mockImplementationOnce(getMockIndicesFxn(testIndices));
getIndicesMock.mockImplementationOnce(getMockIndicesFxn(testBackingIndex));
getDatastreamsMock = jest.fn(getMockDatastreamsFxn(additionalDatastreams));
esClientMock = {
indices: {
get: getIndicesMock,
getDataStream: getDatastreamsMock,
},
} as unknown as ElasticsearchClient;
});
it('returns the correct indices', async () => {
const indices = await getPreEightEnterpriseSearchIndices(esClientMock);
@ -203,16 +199,37 @@ describe('getPreEightEnterpriseSearchIndices', () => {
{
name: '.ent-search-with_same_data_stream',
hasDatastream: true,
datastreams: ['datastream-testing', 'logs-app_search.testdatastream'],
datastreams: ['datastream-testing'],
},
{
name: '.ds-some-other-backing-index',
hasDatastream: true,
datastreams: ['logs-app_search.testdatastream'],
},
]);
expect(getIndicesMock).toHaveBeenCalledTimes(2);
expect(getIndicesMock).toHaveBeenNthCalledWith(1, {
expand_wildcards: ['all', 'hidden'],
ignore_unavailable: true,
index: '.ent-search-*',
});
expect(getIndicesMock).toHaveBeenNthCalledWith(2, {
ignore_unavailable: true,
index: ['.ds-some-other-backing-index'],
});
expect(getDatastreamsMock).toHaveBeenCalledTimes(1);
expect(getDatastreamsMock).toHaveBeenCalledWith({
expand_wildcards: ['all', 'hidden'],
name: 'logs-enterprise_search.*,logs-app_search.*,logs-workplace_search.*',
});
});
});
describe('setPreEightEnterpriseSearchIndicesReadOnly', () => {
it('does not rollover datastreams if there are none', async () => {
const getIndicesMock = getMockIndicesFxn(testIndicesWithoutDatastream);
const getDatastreamsMock = jest.fn(() => Promise.resolve({}));
const getIndicesMock = jest.fn(getMockIndicesFxn(testIndicesWithoutDatastream));
const getDatastreamsMock = jest.fn(() => Promise.resolve({ data_streams: [] }));
const rolloverMock = jest.fn(() => Promise.resolve(true));
const addBlockMock = jest.fn(() => Promise.resolve({ acknowledged: true }));
const esClientMock = {
@ -232,7 +249,12 @@ describe('setPreEightEnterpriseSearchIndicesReadOnly', () => {
});
it('does rollover datastreams if there are any', async () => {
const getIndicesMock = getMockIndicesFxn(testIndices);
const getIndicesMock = jest.fn();
getIndicesMock.mockImplementationOnce(getMockIndicesFxn(testIndices));
getIndicesMock.mockImplementationOnce(getMockIndicesFxn(testBackingIndex));
getIndicesMock.mockImplementationOnce(getMockIndicesFxn(testIndices));
getIndicesMock.mockImplementationOnce(getMockIndicesFxn(testBackingIndex));
const getDatastreamsMock = getMockDatastreamsFxn(additionalDatastreams);
const rolloverMock = jest.fn(() => Promise.resolve(true));
const addBlockMock = jest.fn(() => Promise.resolve({ acknowledged: true }));
@ -247,8 +269,8 @@ describe('setPreEightEnterpriseSearchIndicesReadOnly', () => {
const result = await setPreEightEnterpriseSearchIndicesReadOnly(esClientMock);
expect(result).toEqual('');
expect(getIndicesMock).toHaveBeenCalledTimes(2);
expect(getIndicesMock).toHaveBeenCalledTimes(4);
expect(rolloverMock).toHaveBeenCalledTimes(3);
expect(addBlockMock).toHaveBeenCalledTimes(4);
expect(addBlockMock).toHaveBeenCalledTimes(5);
});
});

View file

@ -5,10 +5,16 @@
* 2.0.
*/
import { IndicesIndexState } from '@elastic/elasticsearch/lib/api/types';
import { ElasticsearchClient } from '@kbn/core/server';
const ENT_SEARCH_INDEX_PREFIX = '.ent-search-';
const ENT_SEARCH_DATASTREAM_PATTERN = [
export const ENT_SEARCH_INDEX_PREFIX = '.ent-search-';
export const ENT_SEARCH_DATASTREAM_PREFIXES = [
'logs-enterprise_search.',
'logs-app_search.',
'logs-workplace_search.',
];
export const ENT_SEARCH_DATASTREAM_PATTERN = [
'logs-enterprise_search.*',
'logs-app_search.*',
'logs-workplace_search.*',
@ -20,6 +26,13 @@ export interface EnterpriseSearchIndexMapping {
datastreams: string[];
}
function is7xIncompatibleIndex(indexData: IndicesIndexState): boolean {
const isReadOnly = indexData.settings?.index?.verified_read_only ?? 'false';
return Boolean(
indexData.settings?.index?.version?.created?.startsWith('7') && isReadOnly !== 'true'
);
}
export async function getPreEightEnterpriseSearchIndices(
esClient: ElasticsearchClient
): Promise<EnterpriseSearchIndexMapping[]> {
@ -29,15 +42,10 @@ export async function getPreEightEnterpriseSearchIndices(
expand_wildcards: ['all', 'hidden'],
});
const entSearchDatastreams = await esClient.indices.getDataStream({
name: ENT_SEARCH_DATASTREAM_PATTERN.join(','),
expand_wildcards: ['all', 'hidden'],
});
const returnIndices: EnterpriseSearchIndexMapping[] = [];
for (const [index, indexData] of Object.entries(entSearchIndices)) {
const isReadOnly = indexData.settings?.index?.verified_read_only ?? 'false';
if (indexData.settings?.index?.version?.created?.startsWith('7') && isReadOnly !== 'true') {
if (is7xIncompatibleIndex(indexData)) {
const dataStreamName = indexData.data_stream;
returnIndices.push({
name: index,
@ -47,22 +55,40 @@ export async function getPreEightEnterpriseSearchIndices(
}
}
for (const [datastream, datastreamData] of Object.entries(entSearchDatastreams)) {
if (!datastreamData.indices || datastreamData.indices.length === 0) {
continue;
}
const { data_streams: entSearchDatastreams } = await esClient.indices.getDataStream({
name: ENT_SEARCH_DATASTREAM_PATTERN.join(','),
expand_wildcards: ['all', 'hidden'],
});
// only get the last index, as this the current write index
const lastIndexName = datastreamData.indices[datastreamData.indices.length - 1].index_name;
const existingIndex = returnIndices.find((index) => index.name === lastIndexName);
if (existingIndex) {
existingIndex.hasDatastream = true;
existingIndex.datastreams.push(datastream);
} else {
const dsIndices = new Set<string>();
entSearchDatastreams.forEach(({ indices: dsi }) => {
dsi.forEach(({ index_name: indexName }) => {
dsIndices.add(indexName);
});
});
if (!dsIndices.size) return returnIndices;
for (const returnIndex of returnIndices) {
if (dsIndices.has(returnIndex.name)) {
dsIndices.delete(returnIndex.name);
}
}
if (!dsIndices.size) return returnIndices;
const entSearchDsIndices = await esClient.indices.get({
index: Array.from(dsIndices.values()),
ignore_unavailable: true,
});
for (const [index, indexData] of Object.entries(entSearchDsIndices)) {
if (is7xIncompatibleIndex(indexData)) {
const dataStreamName = indexData.data_stream;
returnIndices.push({
name: lastIndexName,
hasDatastream: true,
datastreams: [datastream],
name: index,
hasDatastream: dataStreamName ? true : false,
datastreams: [dataStreamName ?? ''],
});
}
}

View file

@ -202,7 +202,19 @@ Object {
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-9.0.html",
},
Object {
"correctiveAction": undefined,
"correctiveAction": Object {
"metadata": Object {
"ignoredIndicesRequiringUpgrade": Array [],
"ignoredIndicesRequiringUpgradeCount": 0,
"indicesRequiringUpgrade": Array [
".ds-some-backing-index-5-2024.11.07-000001",
],
"indicesRequiringUpgradeCount": 1,
"reindexRequired": true,
"totalBackingIndices": 2,
},
"type": "dataStream",
},
"details": "This data stream has backing indices that were created before Elasticsearch 8.0.0",
"index": "my-v7-data-stream",
"isCritical": true,

View file

@ -284,7 +284,7 @@ describe('getESUpgradeStatus', () => {
expect(upgradeStatus.totalCriticalDeprecations).toBe(1);
});
it('filters out old index deprecations enterprise search indices', async () => {
it('filters out old index deprecations enterprise search indices and data streams', async () => {
esClient.asCurrentUser.migration.deprecations.mockResponse({
cluster_settings: [],
node_settings: [],
@ -321,7 +321,24 @@ describe('getESUpgradeStatus', () => {
},
],
},
data_streams: {},
data_streams: {
'logs-workplace_search.test': [
{
level: 'critical',
message: 'Old data stream with a compatibility version < 8.0',
url: 'https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-9.0.html',
details:
'This data stream has backing indices that were created before Elasticsearch 8.0.0',
resolve_during_rolling_upgrade: false,
_meta: {
indices_requiring_upgrade: ['.ds-some-backing-index-5-2024.11.07-000001'],
indices_requiring_upgrade_count: 1,
total_backing_indices: 2,
reindex_required: true,
},
},
],
},
// @ts-expect-error not in types yet
ilm_policies: {},
templates: {},
@ -333,7 +350,10 @@ describe('getESUpgradeStatus', () => {
expect(upgradeStatus.migrationsDeprecations).toHaveLength(2);
expect(
upgradeStatus.migrationsDeprecations.find((dep) => dep.correctiveAction?.type === 'reindex')
upgradeStatus.migrationsDeprecations.find(
(dep) =>
dep.correctiveAction?.type === 'reindex' || dep.correctiveAction?.type === 'dataStream'
)
).toBeUndefined();
expect(

View file

@ -22,6 +22,7 @@ import {
isFrozenDeprecation,
} from './get_corrective_actions';
import { esIndicesStateCheck } from '../es_indices_state_check';
import { ENT_SEARCH_DATASTREAM_PREFIXES, ENT_SEARCH_INDEX_PREFIX } from '../enterprise_search';
/**
* Remove once the these keys are added to the `MigrationDeprecationsResponse` type
@ -123,8 +124,6 @@ const normalizeEsResponse = (migrationsResponse: EsDeprecations) => {
].flat();
};
const ENT_SEARCH_INDICES_PREFIX = '.ent-search-';
export const getEnrichedDeprecations = async (
dataClient: IScopedClusterClient
): Promise<EnrichedDeprecationInfo[]> => {
@ -173,11 +172,15 @@ export const getEnrichedDeprecations = async (
deprecation.index
);
// Early exclusion of deprecations
if (
// Early exclusion of enterprise search indices that need to be reindexed
deprecation.index &&
deprecation.index.startsWith(ENT_SEARCH_INDICES_PREFIX) &&
correctiveAction?.type === 'reindex'
(deprecation.type === 'index_settings' &&
correctiveAction?.type === 'reindex' &&
deprecation.index?.startsWith(ENT_SEARCH_INDEX_PREFIX)) ||
(deprecation.type === 'data_streams' &&
correctiveAction?.type === 'dataStream' &&
correctiveAction.metadata.reindexRequired &&
ENT_SEARCH_DATASTREAM_PREFIXES.some((prefix) => deprecation.index?.startsWith(prefix)))
) {
return [];
}