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] Ensure Enterprise Search pre 8.x Datastream Deprecations #211847

Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* 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 { DeprecationDetailsMessage, DeprecationsDetails } from '@kbn/core-deprecations-common';
import { GetDeprecationsContext } from '@kbn/core-deprecations-server';

import { getEnterpriseSearchPre8IndexDeprecations } from './enterprise_search_deprecations';
import indexDeprecatorFxns = require('./pre_eight_index_deprecator');

const ctx = {
esClient: {
asInternalUser: {},
},
} as GetDeprecationsContext;

function getMessageFromDeprecation(details: DeprecationsDetails): string {
const message = details.message as DeprecationDetailsMessage;
return message.content;
}

describe('getEnterpriseSearchPre8IndexDeprecations', () => {
it('can register index and data stream deprecations that need to be set to read only', async () => {
const getIndicesMock = jest.fn(() =>
Promise.resolve([
{
name: '.ent-search-index_without_datastream',
hasDatastream: false,
datastreams: [],
},
{
name: '.ent-search-with_data_stream',
hasDatastream: true,
datastreams: ['datastream-testing'],
},
])
);

jest
.spyOn(indexDeprecatorFxns, 'getPreEightEnterpriseSearchIndices')
.mockImplementation(getIndicesMock);

const deprecations = await getEnterpriseSearchPre8IndexDeprecations(ctx, 'docsurl');
expect(deprecations).toHaveLength(1);
expect(deprecations[0].correctiveActions.api?.path).toStrictEqual(
'/internal/enterprise_search/deprecations/set_enterprise_search_indices_read_only'
);
expect(deprecations[0].title).toMatch('Pre 8.x Enterprise Search indices compatibility');
expect(getMessageFromDeprecation(deprecations[0])).toContain(
'The following indices are found to be incompatible for upgrade'
);
expect(getMessageFromDeprecation(deprecations[0])).toContain(
'.ent-search-index_without_datastream'
);
expect(getMessageFromDeprecation(deprecations[0])).toContain(
'The following data streams are found to be incompatible for upgrade'
);
expect(getMessageFromDeprecation(deprecations[0])).toContain('.ent-search-with_data_stream');
});

it('can register an index without data stream deprecations that need to be set to read only', async () => {
const getIndicesMock = jest.fn(() =>
Promise.resolve([
{
name: '.ent-search-index_without_datastream',
hasDatastream: false,
datastreams: [''],
},
])
);

jest
.spyOn(indexDeprecatorFxns, 'getPreEightEnterpriseSearchIndices')
.mockImplementation(getIndicesMock);

const deprecations = await getEnterpriseSearchPre8IndexDeprecations(ctx, 'docsurl');
expect(deprecations).toHaveLength(1);
expect(deprecations[0].correctiveActions.api?.path).toStrictEqual(
'/internal/enterprise_search/deprecations/set_enterprise_search_indices_read_only'
);
expect(deprecations[0].title).toMatch('Pre 8.x Enterprise Search indices compatibility');
expect(getMessageFromDeprecation(deprecations[0])).toContain(
'The following indices are found to be incompatible for upgrade'
);
expect(getMessageFromDeprecation(deprecations[0])).toContain(
'.ent-search-index_without_datastream'
);
expect(getMessageFromDeprecation(deprecations[0])).not.toContain(
'The following data streams are found to be incompatible for upgrade'
);
expect(getMessageFromDeprecation(deprecations[0])).not.toContain(
'.ent-search-with_data_stream'
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* 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 { DeprecationsDetails } from '@kbn/core-deprecations-common';
import { GetDeprecationsContext, RegisterDeprecationsConfig } from '@kbn/core-deprecations-server';

import { i18n } from '@kbn/i18n';
import { getPreEightEnterpriseSearchIndices } from './pre_eight_index_deprecator';

export const getEntepriseSearchRegisteredDeprecations = (
docsUrl: string
): RegisterDeprecationsConfig => {
return {
getDeprecations: async (ctx: GetDeprecationsContext) => {
const [entSearchIndexIncompatibility] = await Promise.all([
getEnterpriseSearchPre8IndexDeprecations(ctx, docsUrl),
]);
return [...entSearchIndexIncompatibility];
},
};
};

/**
* If there are any Enterprise Search indices that were created with Elasticsearch 7.x, they must be removed
* or set to read-only
*/
export async function getEnterpriseSearchPre8IndexDeprecations(
ctx: GetDeprecationsContext,
docsUrl: string
): Promise<DeprecationsDetails[]> {
const deprecations: DeprecationsDetails[] = [];

const entSearchIndices = await getPreEightEnterpriseSearchIndices(ctx.esClient.asInternalUser);
if (!entSearchIndices || entSearchIndices.length === 0) {
return deprecations;
}

let indicesList = '';
let datastreamsList = '';
for (const index of entSearchIndices) {
if (index.hasDatastream) {
indicesList += `${index.name}\n`;
for (const datastream of index.datastreams) {
if (datastream === '') continue;
datastreamsList += `${datastream}\n`;
}
} else {
indicesList += `${index.name}\n`;
}
}

let message = `There are ${entSearchIndices.length} incompatible Enterprise Search indices.\n\n`;

if (indicesList.length > 0) {
message +=
'The following indices are found to be incompatible for upgrade:\n\n' +
'```\n' +
`${indicesList}` +
'\n```\n' +
'These indices must be either set to read-only or deleted before upgrading.\n';
}

if (datastreamsList.length > 0) {
message +=
'\nThe following data streams are found to be incompatible for upgrade:\n\n' +
'```\n' +
`${datastreamsList}` +
'\n```\n' +
'Using the "quick resolve" button below will roll over any datastreams and set all incompatible indices to read-only.\n\n' +
'Alternatively, manually deleting these indices and data streams will also unblock your upgrade.';
} else {
message +=
'Setting these indices to read-only can be attempted with the "quick resolve" button below.\n\n' +
'Alternatively, manually deleting these indices will also unblock your upgrade.';
}

deprecations.push({
level: 'critical',
deprecationType: 'feature',
title: i18n.translate(
'xpack.upgradeAssistant.deprecations.incompatibleEnterpriseSearchIndexes.title',
{
defaultMessage: 'Pre 8.x Enterprise Search indices compatibility',
}
),
message: {
type: 'markdown',
content: i18n.translate(
'xpack.upgradeAssistant.deprecations.incompatibleEnterpriseSearchIndexes.message',
{
defaultMessage: message,
}
),
},
documentationUrl: docsUrl,
correctiveActions: {
manualSteps: [
i18n.translate(
'xpack.upgradeAssistant.deprecations.incompatibleEnterpriseSearchIndexes.deleteIndices',
{
defaultMessage: 'Set all incompatible indices and data streams to read only, or',
}
),
i18n.translate(
'xpack.upgradeAssistant.deprecations.incompatibleEnterpriseSearchIndexes.deleteIndices',
{
defaultMessage: 'Delete all incompatible indices and data streams',
}
),
],
api: {
method: 'POST',
path: '/internal/enterprise_search/deprecations/set_enterprise_search_indices_read_only',
body: {
deprecationDetails: { domainId: 'enterpriseSearch' },
},
},
},
});

return deprecations;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* 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 { kibanaResponseFactory } from '@kbn/core/server';

import { handleEsError } from '../../shared_imports';
import {
createMockRouter,
MockRouter,
routeHandlerContextMock,
} from '../../routes/__mocks__/routes.mock';
import { createRequestMock } from '../../routes/__mocks__/request.mock';

jest.mock('../es_version_precheck', () => ({
versionCheckHandlerWrapper: (a: any) => a,
}));

import indexDeprecatorFxns = require('./pre_eight_index_deprecator');

import { registerEnterpriseSearchDeprecationRoutes } from './enterprise_search_deprecations_routes';

describe('deprecation routes', () => {
let routeDependencies: any;

describe('POST /internal/enterprise_search/deprecations/set_enterprise_search_indices_read_only', () => {
let mockRouter: MockRouter;

function registerMockRouter({ mlSnapshots } = { mlSnapshots: true }) {
mockRouter = createMockRouter();
routeDependencies = {
config: {
featureSet: { mlSnapshots, migrateSystemIndices: true, reindexCorrectiveActions: true },
},
router: mockRouter,
lib: { handleEsError },
};
registerEnterpriseSearchDeprecationRoutes(routeDependencies);
}

beforeEach(() => {
registerMockRouter();
});

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

it('sets read-only and 200s correctly in happy path', async () => {
const setIndicesReadOnlyMock = jest.spyOn(
indexDeprecatorFxns,
'setPreEightEnterpriseSearchIndicesReadOnly'
);

setIndicesReadOnlyMock.mockResolvedValue('');

const resp = await routeDependencies.router.getHandler({
method: 'post',
pathPattern:
'/internal/enterprise_search/deprecations/set_enterprise_search_indices_read_only',
})(
routeHandlerContextMock,
createRequestMock({
body: { deprecationDetails: { domainId: 'enterpriseSearch' } },
}),
kibanaResponseFactory
);

expect(resp.status).toEqual(200);
expect(resp.payload).toEqual({
acknowedged: true,
});

expect(setIndicesReadOnlyMock).toHaveBeenCalledTimes(1);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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 { setPreEightEnterpriseSearchIndicesReadOnly } from './pre_eight_index_deprecator';
import { versionCheckHandlerWrapper } from '../es_version_precheck';
import { RouteDependencies } from '../../types';

export function registerEnterpriseSearchDeprecationRoutes({
config: { featureSet },
router,
lib: { handleEsError },
licensing,
log,
}: RouteDependencies) {
router.post(
{
path: '/internal/enterprise_search/deprecations/set_enterprise_search_indices_read_only',
validate: {},
},
versionCheckHandlerWrapper(async ({ core }, request, response) => {
const { client } = (await core).elasticsearch;
const setResponse = await setPreEightEnterpriseSearchIndicesReadOnly(client.asCurrentUser);
if (setResponse.length > 0) {
return response.badRequest({
body: { message: setResponse },
headers: { 'content-type': 'application/json' },
});
}
return response.ok({
body: { acknowedged: true },
headers: { 'content-type': 'application/json' },
});
})
);
}
Loading