mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
# Backport This will backport the following commits from `main` to `8.7`: - [[Kibana migrator test kit] Flush logger after running migrations (#152818)](https://github.com/elastic/kibana/pull/152818) <!--- Backport version: 8.9.7 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Gerard Soldevila","email":"gerard.soldevila@elastic.co"},"sourceCommit":{"committedDate":"2023-03-07T17:57:17Z","message":"[Kibana migrator test kit] Flush logger after running migrations (#152818)\n\nThis PR addresses flakiness of:\r\n\r\n- https://github.com/elastic/kibana/issues/152472\r\n- https://github.com/elastic/kibana/issues/152448\r\n\r\nThese tests fail because we have a race condition. We are checking\r\ncertain conditions in the log files, and sometimes we do it before the\r\nlogs are actually written.\r\n\r\nWith the Kibana migrator test kit, we can actually flush + wait for all\r\nthe logging appenders. This way, we are sure that the logs are complete\r\nwhen we inspect them in the tests.\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"163dae7154c11d92ff148bc6ac45cbea54680769","branchLabelMapping":{"^v8.8.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","Team:Core","release_note:skip","test-failure-flaky","Feature:Migrations","backport:prev-minor","v8.7.0","v8.8.0"],"number":152818,"url":"https://github.com/elastic/kibana/pull/152818","mergeCommit":{"message":"[Kibana migrator test kit] Flush logger after running migrations (#152818)\n\nThis PR addresses flakiness of:\r\n\r\n- https://github.com/elastic/kibana/issues/152472\r\n- https://github.com/elastic/kibana/issues/152448\r\n\r\nThese tests fail because we have a race condition. We are checking\r\ncertain conditions in the log files, and sometimes we do it before the\r\nlogs are actually written.\r\n\r\nWith the Kibana migrator test kit, we can actually flush + wait for all\r\nthe logging appenders. This way, we are sure that the logs are complete\r\nwhen we inspect them in the tests.\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"163dae7154c11d92ff148bc6ac45cbea54680769"}},"sourceBranch":"main","suggestedTargetBranches":["8.7"],"targetPullRequestStates":[{"branch":"8.7","label":"v8.7.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.8.0","labelRegex":"^v8.8.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/152818","number":152818,"mergeCommit":{"message":"[Kibana migrator test kit] Flush logger after running migrations (#152818)\n\nThis PR addresses flakiness of:\r\n\r\n- https://github.com/elastic/kibana/issues/152472\r\n- https://github.com/elastic/kibana/issues/152448\r\n\r\nThese tests fail because we have a race condition. We are checking\r\ncertain conditions in the log files, and sometimes we do it before the\r\nlogs are actually written.\r\n\r\nWith the Kibana migrator test kit, we can actually flush + wait for all\r\nthe logging appenders. This way, we are sure that the logs are complete\r\nwhen we inspect them in the tests.\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"163dae7154c11d92ff148bc6ac45cbea54680769"}}]}] BACKPORT-->
This commit is contained in:
parent
db3b8c3108
commit
e70452f969
4 changed files with 462 additions and 233 deletions
|
@ -10,28 +10,174 @@ import Path from 'path';
|
|||
import Fs from 'fs';
|
||||
import Util from 'util';
|
||||
import JSON5 from 'json5';
|
||||
import { type TestElasticsearchUtils } from '@kbn/core-test-helpers-kbn-server';
|
||||
import { SavedObjectsType } from '@kbn/core-saved-objects-server';
|
||||
import { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
|
||||
import { getMigrationDocLink, delay } from '../test_utils';
|
||||
import {
|
||||
createTestServers,
|
||||
createRootWithCorePlugins,
|
||||
type TestElasticsearchUtils,
|
||||
} from '@kbn/core-test-helpers-kbn-server';
|
||||
import { Root } from '@kbn/core-root-server-internal';
|
||||
import { getMigrationDocLink } from '../test_utils';
|
||||
clearLog,
|
||||
currentVersion,
|
||||
defaultKibanaIndex,
|
||||
getKibanaMigratorTestKit,
|
||||
nextMinor,
|
||||
startElasticsearch,
|
||||
} from '../kibana_migrator_test_kit';
|
||||
|
||||
const migrationDocLink = getMigrationDocLink().resolveMigrationFailures;
|
||||
const logFilePath = Path.join(__dirname, 'cleanup.log');
|
||||
|
||||
const asyncUnlink = Util.promisify(Fs.unlink);
|
||||
const asyncReadFile = Util.promisify(Fs.readFile);
|
||||
|
||||
async function removeLogFile() {
|
||||
// ignore errors if it doesn't exist
|
||||
await asyncUnlink(logFilePath).catch(() => void 0);
|
||||
}
|
||||
describe('migration v2', () => {
|
||||
let esServer: TestElasticsearchUtils['es'];
|
||||
let esClient: ElasticsearchClient;
|
||||
|
||||
function createRoot() {
|
||||
return createRootWithCorePlugins(
|
||||
beforeAll(async () => {
|
||||
esServer = await startElasticsearch();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
esClient = await setupBaseline();
|
||||
await clearLog(logFilePath);
|
||||
});
|
||||
|
||||
it('clean ups if migration fails', async () => {
|
||||
const { runMigrations, client } = await setupNextMinor();
|
||||
|
||||
await expect(runMigrations()).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||
"Unable to complete saved object migrations for the [${defaultKibanaIndex}] index: Migrations failed. Reason: 1 corrupt saved object documents were found: corrupt:2baf4de0-a6d4-11ed-ba5a-39196fc76e60
|
||||
|
||||
To allow migrations to proceed, please delete or fix these documents.
|
||||
Note that you can configure Kibana to automatically discard corrupt documents and transform errors for this migration.
|
||||
Please refer to ${migrationDocLink} for more information."
|
||||
`);
|
||||
|
||||
const logFileContent = await asyncReadFile(logFilePath, 'utf-8');
|
||||
const records = logFileContent
|
||||
.split('\n')
|
||||
.filter(Boolean)
|
||||
.map((str) => JSON5.parse(str));
|
||||
|
||||
const logRecordWithPit = records.find(
|
||||
(rec) => rec.message === `[${defaultKibanaIndex}] REINDEX_SOURCE_TO_TEMP_OPEN_PIT RESPONSE`
|
||||
);
|
||||
|
||||
expect(logRecordWithPit).toBeTruthy();
|
||||
|
||||
const pitId = logRecordWithPit.right.pitId;
|
||||
expect(pitId).toBeTruthy();
|
||||
|
||||
await expect(
|
||||
client.search({
|
||||
body: {
|
||||
pit: { id: pitId },
|
||||
},
|
||||
})
|
||||
// throws an exception that cannot search with closed PIT
|
||||
).rejects.toThrow(/search_phase_execution_exception/);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await esClient?.indices.delete({ index: `${defaultKibanaIndex}_${currentVersion}_001` });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await esServer?.stop();
|
||||
await delay(10);
|
||||
});
|
||||
});
|
||||
|
||||
const setupBaseline = async () => {
|
||||
const typesCurrent: SavedObjectsType[] = [
|
||||
{
|
||||
name: 'complex',
|
||||
hidden: false,
|
||||
namespaceType: 'agnostic',
|
||||
mappings: {
|
||||
properties: {
|
||||
name: { type: 'text' },
|
||||
value: { type: 'integer' },
|
||||
},
|
||||
},
|
||||
migrations: {},
|
||||
},
|
||||
];
|
||||
|
||||
const savedObjects = [
|
||||
{
|
||||
id: 'complex:4baf4de0-a6d4-11ed-ba5a-39196fc76e60',
|
||||
body: {
|
||||
type: 'complex',
|
||||
complex: {
|
||||
name: 'foo',
|
||||
value: 5,
|
||||
},
|
||||
references: [],
|
||||
coreMigrationVersion: currentVersion,
|
||||
updated_at: '2023-02-07T11:04:44.914Z',
|
||||
created_at: '2023-02-07T11:04:44.914Z',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'corrupt:2baf4de0-a6d4-11ed-ba5a-39196fc76e60', // incorrect id => corrupt object
|
||||
body: {
|
||||
type: 'complex',
|
||||
complex: {
|
||||
name: 'bar',
|
||||
value: 3,
|
||||
},
|
||||
references: [],
|
||||
coreMigrationVersion: currentVersion,
|
||||
updated_at: '2023-02-07T11:04:44.914Z',
|
||||
created_at: '2023-02-07T11:04:44.914Z',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const { runMigrations, client } = await getKibanaMigratorTestKit({
|
||||
types: typesCurrent,
|
||||
logFilePath,
|
||||
});
|
||||
|
||||
await runMigrations();
|
||||
|
||||
// inject corrupt saved objects directly using esClient
|
||||
await Promise.all(
|
||||
savedObjects.map((savedObject) => {
|
||||
client.create({
|
||||
index: defaultKibanaIndex,
|
||||
refresh: 'wait_for',
|
||||
...savedObject,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
return client;
|
||||
};
|
||||
|
||||
const setupNextMinor = async () => {
|
||||
const typesNextMinor: SavedObjectsType[] = [
|
||||
{
|
||||
name: 'complex',
|
||||
hidden: false,
|
||||
namespaceType: 'agnostic',
|
||||
mappings: {
|
||||
properties: {
|
||||
name: { type: 'keyword' },
|
||||
value: { type: 'long' },
|
||||
},
|
||||
},
|
||||
migrations: {
|
||||
[nextMinor]: (doc) => doc,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return await getKibanaMigratorTestKit({
|
||||
types: typesNextMinor,
|
||||
kibanaVersion: nextMinor,
|
||||
logFilePath,
|
||||
settings: {
|
||||
migrations: {
|
||||
skip: false,
|
||||
},
|
||||
|
@ -54,102 +200,5 @@ function createRoot() {
|
|||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
oss: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
describe('migration v2', () => {
|
||||
let esServer: TestElasticsearchUtils;
|
||||
let root: Root;
|
||||
|
||||
beforeAll(async () => {
|
||||
await removeLogFile();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (root) {
|
||||
await root.shutdown();
|
||||
}
|
||||
if (esServer) {
|
||||
await esServer.stop();
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 10000));
|
||||
});
|
||||
|
||||
it('clean ups if migration fails', async () => {
|
||||
const { startES } = createTestServers({
|
||||
adjustTimeout: (t: number) => jest.setTimeout(t),
|
||||
settings: {
|
||||
es: {
|
||||
license: 'basic',
|
||||
// original SO:
|
||||
// {
|
||||
// _index: '.kibana_7.13.0_001',
|
||||
// _type: '_doc',
|
||||
// _id: 'index-pattern:test_index*',
|
||||
// _version: 1,
|
||||
// result: 'created',
|
||||
// _shards: { total: 2, successful: 1, failed: 0 },
|
||||
// _seq_no: 0,
|
||||
// _primary_term: 1
|
||||
// }
|
||||
dataArchive: Path.join(__dirname, '..', 'archives', '7.13.0_with_corrupted_so.zip'),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
root = createRoot();
|
||||
|
||||
esServer = await startES();
|
||||
await root.preboot();
|
||||
const coreSetup = await root.setup();
|
||||
|
||||
coreSetup.savedObjects.registerType({
|
||||
name: 'foo',
|
||||
hidden: false,
|
||||
mappings: {
|
||||
properties: {},
|
||||
},
|
||||
namespaceType: 'agnostic',
|
||||
migrations: {
|
||||
'7.14.0': (doc) => doc,
|
||||
},
|
||||
});
|
||||
|
||||
await expect(root.start()).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||
"Unable to complete saved object migrations for the [.kibana] index: Migrations failed. Reason: 1 corrupt saved object documents were found: index-pattern:test_index*
|
||||
|
||||
To allow migrations to proceed, please delete or fix these documents.
|
||||
Note that you can configure Kibana to automatically discard corrupt documents and transform errors for this migration.
|
||||
Please refer to ${migrationDocLink} for more information."
|
||||
`);
|
||||
|
||||
const logFileContent = await asyncReadFile(logFilePath, 'utf-8');
|
||||
const records = logFileContent
|
||||
.split('\n')
|
||||
.filter(Boolean)
|
||||
.map((str) => JSON5.parse(str));
|
||||
|
||||
const logRecordWithPit = records.find(
|
||||
(rec) => rec.message === '[.kibana] REINDEX_SOURCE_TO_TEMP_OPEN_PIT RESPONSE'
|
||||
);
|
||||
|
||||
expect(logRecordWithPit).toBeTruthy();
|
||||
|
||||
const pitId = logRecordWithPit.right.pitId;
|
||||
expect(pitId).toBeTruthy();
|
||||
|
||||
const client = esServer.es.getClient();
|
||||
await expect(
|
||||
client.search({
|
||||
body: {
|
||||
pit: { id: pitId },
|
||||
},
|
||||
})
|
||||
// throws an exception that cannot search with closed PIT
|
||||
).rejects.toThrow(/search_phase_execution_exception/);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -5,121 +5,105 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import Path from 'path';
|
||||
import fs from 'fs/promises';
|
||||
import { Env } from '@kbn/config';
|
||||
import { getEnvOptions } from '@kbn/config-mocks';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import type { Root } from '@kbn/core-root-server-internal';
|
||||
|
||||
import type { TestElasticsearchUtils } from '@kbn/core-test-helpers-kbn-server';
|
||||
import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
|
||||
import type { MigrationResult } from '@kbn/core-saved-objects-base-server-internal';
|
||||
import {
|
||||
createRootWithCorePlugins,
|
||||
createTestServers,
|
||||
type TestElasticsearchUtils,
|
||||
} from '@kbn/core-test-helpers-kbn-server';
|
||||
readLog,
|
||||
clearLog,
|
||||
createBaseline,
|
||||
currentVersion,
|
||||
defaultKibanaIndex,
|
||||
getIdenticalMappingsMigrator,
|
||||
getIncompatibleMappingsMigrator,
|
||||
startElasticsearch,
|
||||
} from '../kibana_migrator_test_kit';
|
||||
import { delay } from '../test_utils';
|
||||
import { SemVer } from 'semver';
|
||||
|
||||
const logFilePath = Path.join(__dirname, 'skip_reindex.log');
|
||||
|
||||
describe('skip reindexing', () => {
|
||||
const currentVersion = Env.createDefault(REPO_ROOT, getEnvOptions()).packageInfo.version;
|
||||
describe('when migrating to a new version', () => {
|
||||
let esServer: TestElasticsearchUtils['es'];
|
||||
let root: Root;
|
||||
let esClient: ElasticsearchClient;
|
||||
let runMigrations: (rerun?: boolean | undefined) => Promise<MigrationResult[]>;
|
||||
|
||||
beforeAll(async () => {
|
||||
esServer = await startElasticsearch();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
esClient = await createBaseline();
|
||||
await clearLog();
|
||||
});
|
||||
|
||||
describe('and the mappings remain the same', () => {
|
||||
it('the migrator skips reindexing', async () => {
|
||||
// we run the migrator with the same identic baseline types
|
||||
runMigrations = (await getIdenticalMappingsMigrator()).runMigrations;
|
||||
await runMigrations();
|
||||
|
||||
const logs = await readLog();
|
||||
expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE.');
|
||||
expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> CLEANUP_UNKNOWN_AND_EXCLUDED.');
|
||||
expect(logs).toMatch(
|
||||
'CLEANUP_UNKNOWN_AND_EXCLUDED -> CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK.'
|
||||
);
|
||||
expect(logs).toMatch(
|
||||
'CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK -> PREPARE_COMPATIBLE_MIGRATION.'
|
||||
);
|
||||
expect(logs).toMatch('PREPARE_COMPATIBLE_MIGRATION -> REFRESH_TARGET.');
|
||||
expect(logs).toMatch('REFRESH_TARGET -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT.');
|
||||
expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> CHECK_VERSION_INDEX_READY_ACTIONS.');
|
||||
expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> DONE.');
|
||||
|
||||
expect(logs).not.toMatch('CREATE_NEW_TARGET');
|
||||
expect(logs).not.toMatch('CHECK_UNKNOWN_DOCUMENTS');
|
||||
expect(logs).not.toMatch('REINDEX');
|
||||
expect(logs).not.toMatch('UPDATE_TARGET_MAPPINGS_PROPERTIES');
|
||||
});
|
||||
});
|
||||
|
||||
describe('and the mappings have changed', () => {
|
||||
it('the migrator reindexes documents to a new index', async () => {
|
||||
// we run the migrator with altered, compatible mappings
|
||||
runMigrations = (await getIncompatibleMappingsMigrator()).runMigrations;
|
||||
await runMigrations();
|
||||
|
||||
const logs = await readLog();
|
||||
expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE.');
|
||||
expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> CHECK_UNKNOWN_DOCUMENTS.');
|
||||
expect(logs).toMatch('CALCULATE_EXCLUDE_FILTERS -> CREATE_REINDEX_TEMP.');
|
||||
expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS.');
|
||||
expect(logs).toMatch('UPDATE_TARGET_MAPPINGS_META -> CHECK_VERSION_INDEX_READY_ACTIONS.');
|
||||
expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> MARK_VERSION_INDEX_READY.');
|
||||
expect(logs).toMatch('MARK_VERSION_INDEX_READY -> DONE.');
|
||||
|
||||
expect(logs).not.toMatch('CREATE_NEW_TARGET');
|
||||
expect(logs).not.toMatch('CLEANUP_UNKNOWN_AND_EXCLUDED');
|
||||
expect(logs).not.toMatch('PREPARE_COMPATIBLE_MIGRATION');
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await root?.shutdown();
|
||||
// we run the migrator again to ensure that the next time state is loaded everything still works as expected
|
||||
await clearLog();
|
||||
await runMigrations(true);
|
||||
|
||||
const logs = await readLog();
|
||||
expect(logs).toMatch('INIT -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT.');
|
||||
expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> DONE.');
|
||||
|
||||
expect(logs).not.toMatch('WAIT_FOR_YELLOW_SOURCE');
|
||||
expect(logs).not.toMatch('CLEANUP_UNKNOWN_AND_EXCLUCED');
|
||||
expect(logs).not.toMatch('CREATE_NEW_TARGET');
|
||||
expect(logs).not.toMatch('PREPARE_COMPATIBLE_MIGRATION');
|
||||
expect(logs).not.toMatch('UPDATE_TARGET_MAPPINGS_PROPERTIES');
|
||||
|
||||
// clear the system index for next test
|
||||
await esClient?.indices.delete({ index: `${defaultKibanaIndex}_${currentVersion}_001` });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await esServer?.stop();
|
||||
await delay(10);
|
||||
});
|
||||
|
||||
it('when migrating to a new version, but mappings remain the same', async () => {
|
||||
let logs: string;
|
||||
const { startES } = createTestServers({
|
||||
adjustTimeout: (t: number) => jest.setTimeout(t),
|
||||
settings: {
|
||||
es: {
|
||||
license: 'basic',
|
||||
},
|
||||
},
|
||||
});
|
||||
esServer = await startES();
|
||||
root = createRoot();
|
||||
|
||||
// Run initial migrations
|
||||
await root.preboot();
|
||||
await root.setup();
|
||||
await root.start();
|
||||
|
||||
// stop Kibana and remove logs
|
||||
await root.shutdown();
|
||||
await delay(10);
|
||||
await fs.unlink(logFilePath).catch(() => {});
|
||||
|
||||
const nextPatch = new SemVer(currentVersion).inc('patch').format();
|
||||
root = createRoot(nextPatch);
|
||||
await root.preboot();
|
||||
await root.setup();
|
||||
await root.start();
|
||||
|
||||
logs = await fs.readFile(logFilePath, 'utf-8');
|
||||
|
||||
expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE');
|
||||
expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> CLEANUP_UNKNOWN_AND_EXCLUDED');
|
||||
expect(logs).toMatch(
|
||||
'CLEANUP_UNKNOWN_AND_EXCLUDED -> CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK'
|
||||
);
|
||||
expect(logs).toMatch(
|
||||
'CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK -> PREPARE_COMPATIBLE_MIGRATION'
|
||||
);
|
||||
expect(logs).toMatch('PREPARE_COMPATIBLE_MIGRATION -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT');
|
||||
expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> CHECK_VERSION_INDEX_READY_ACTIONS');
|
||||
expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> DONE');
|
||||
|
||||
expect(logs).not.toMatch('CREATE_NEW_TARGET');
|
||||
expect(logs).not.toMatch('CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS');
|
||||
|
||||
// We restart Kibana again after doing a "compatible migration" to ensure that
|
||||
// the next time state is loaded everything still works as expected.
|
||||
// For instance, we might see something like:
|
||||
// Unable to complete saved object migrations for the [.kibana] index. Please check the health of your Elasticsearch cluster and try again. Unexpected Elasticsearch ResponseError: statusCode: 404, method: POST, url: /.kibana_8.7.1_001/_pit?keep_alive=10m error: [index_not_found_exception]: no such index [.kibana_8.7.1_001]
|
||||
await root.shutdown();
|
||||
await delay(10);
|
||||
await fs.unlink(logFilePath).catch(() => {});
|
||||
|
||||
root = createRoot(nextPatch);
|
||||
await root.preboot();
|
||||
await root.setup();
|
||||
await root.start();
|
||||
|
||||
logs = await fs.readFile(logFilePath, 'utf-8');
|
||||
expect(logs).toMatch('INIT -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT');
|
||||
expect(logs).not.toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE');
|
||||
});
|
||||
});
|
||||
|
||||
function createRoot(kibanaVersion?: string): Root {
|
||||
return createRootWithCorePlugins(
|
||||
{
|
||||
logging: {
|
||||
appenders: {
|
||||
file: {
|
||||
type: 'file',
|
||||
fileName: logFilePath,
|
||||
layout: {
|
||||
type: 'json',
|
||||
},
|
||||
},
|
||||
},
|
||||
loggers: [
|
||||
{
|
||||
name: 'root',
|
||||
level: 'info',
|
||||
appenders: ['file'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{ oss: true },
|
||||
kibanaVersion
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { SavedObjectsBulkCreateObject } from '@kbn/core-saved-objects-api-server';
|
||||
import type { SavedObjectsType } from '@kbn/core-saved-objects-server';
|
||||
|
||||
const defaultType: SavedObjectsType<any> = {
|
||||
name: 'defaultType',
|
||||
hidden: false,
|
||||
namespaceType: 'agnostic',
|
||||
mappings: {
|
||||
properties: {
|
||||
name: { type: 'keyword' },
|
||||
},
|
||||
},
|
||||
migrations: {},
|
||||
};
|
||||
|
||||
export const baselineTypes: Array<SavedObjectsType<any>> = [
|
||||
{
|
||||
...defaultType,
|
||||
name: 'server',
|
||||
},
|
||||
{
|
||||
...defaultType,
|
||||
name: 'basic',
|
||||
},
|
||||
{
|
||||
...defaultType,
|
||||
name: 'deprecated',
|
||||
},
|
||||
{
|
||||
...defaultType,
|
||||
name: 'complex',
|
||||
mappings: {
|
||||
properties: {
|
||||
name: { type: 'text' },
|
||||
value: { type: 'integer' },
|
||||
},
|
||||
},
|
||||
excludeOnUpgrade: () => {
|
||||
return {
|
||||
bool: {
|
||||
must: [{ term: { type: 'complex' } }, { range: { 'complex.value': { lte: 1 } } }],
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const baselineDocuments: SavedObjectsBulkCreateObject[] = [
|
||||
...['server-foo', 'server-bar', 'server-baz'].map((name) => ({
|
||||
type: 'server',
|
||||
attributes: {
|
||||
name,
|
||||
},
|
||||
})),
|
||||
...['basic-foo', 'basic-bar', 'basic-baz'].map((name) => ({
|
||||
type: 'basic',
|
||||
attributes: {
|
||||
name,
|
||||
},
|
||||
})),
|
||||
...['deprecated-foo', 'deprecated-bar', 'deprecated-baz'].map((name) => ({
|
||||
type: 'deprecated',
|
||||
attributes: {
|
||||
name,
|
||||
},
|
||||
})),
|
||||
...['complex-foo', 'complex-bar', 'complex-baz', 'complex-lipsum'].map((name, index) => ({
|
||||
type: 'complex',
|
||||
attributes: {
|
||||
name,
|
||||
value: index,
|
||||
},
|
||||
})),
|
||||
];
|
|
@ -7,6 +7,9 @@
|
|||
*/
|
||||
|
||||
import Path from 'path';
|
||||
import fs from 'fs/promises';
|
||||
import { SemVer } from 'semver';
|
||||
|
||||
import { defaultsDeep } from 'lodash';
|
||||
import { BehaviorSubject, firstValueFrom, map } from 'rxjs';
|
||||
import { ConfigService, Env } from '@kbn/config';
|
||||
|
@ -19,8 +22,7 @@ import {
|
|||
type SavedObjectsConfigType,
|
||||
type SavedObjectsMigrationConfigType,
|
||||
SavedObjectTypeRegistry,
|
||||
IKibanaMigrator,
|
||||
MigrationResult,
|
||||
type MigrationResult,
|
||||
} from '@kbn/core-saved-objects-base-server-internal';
|
||||
import { SavedObjectsRepository } from '@kbn/core-saved-objects-api-server-internal';
|
||||
import {
|
||||
|
@ -32,20 +34,23 @@ import { type LoggingConfigType, LoggingSystem } from '@kbn/core-logging-server-
|
|||
|
||||
import type { ISavedObjectTypeRegistry, SavedObjectsType } from '@kbn/core-saved-objects-server';
|
||||
import { esTestConfig, kibanaServerTestUser } from '@kbn/test';
|
||||
import { LoggerFactory } from '@kbn/logging';
|
||||
import type { LoggerFactory } from '@kbn/logging';
|
||||
import { createTestServers } from '@kbn/core-test-helpers-kbn-server';
|
||||
import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
|
||||
import { registerServiceConfig } from '@kbn/core-root-server-internal';
|
||||
import { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server';
|
||||
import type { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server';
|
||||
import { getDocLinks, getDocLinksMeta } from '@kbn/doc-links';
|
||||
import { DocLinksServiceStart } from '@kbn/core-doc-links-server';
|
||||
import { createTestServers } from '@kbn/core-test-helpers-kbn-server';
|
||||
import type { DocLinksServiceStart } from '@kbn/core-doc-links-server';
|
||||
import { baselineDocuments, baselineTypes } from './kibana_migrator_test_kit.fixtures';
|
||||
|
||||
export const defaultLogFilePath = Path.join(__dirname, 'kibana_migrator_test_kit.log');
|
||||
|
||||
const env = Env.createDefault(REPO_ROOT, getEnvOptions());
|
||||
// Extract current stack version from Env, to use as a default
|
||||
const currentVersion = env.packageInfo.version;
|
||||
const currentBranch = env.packageInfo.branch;
|
||||
export const currentVersion = env.packageInfo.version;
|
||||
export const nextMinor = new SemVer(currentVersion).inc('minor').format();
|
||||
export const currentBranch = env.packageInfo.branch;
|
||||
export const defaultKibanaIndex = '.kibana_migrator_tests';
|
||||
|
||||
export interface GetEsClientParams {
|
||||
settings?: Record<string, any>;
|
||||
|
@ -64,7 +69,6 @@ export interface KibanaMigratorTestKitParams {
|
|||
|
||||
export interface KibanaMigratorTestKit {
|
||||
client: ElasticsearchClient;
|
||||
migrator: IKibanaMigrator;
|
||||
runMigrations: (rerun?: boolean) => Promise<MigrationResult[]>;
|
||||
typeRegistry: ISavedObjectTypeRegistry;
|
||||
savedObjectsRepository: ISavedObjectsRepository;
|
||||
|
@ -76,7 +80,7 @@ export const startElasticsearch = async ({
|
|||
}: {
|
||||
basePath?: string;
|
||||
dataArchive?: string;
|
||||
}) => {
|
||||
} = {}) => {
|
||||
const { startES } = createTestServers({
|
||||
adjustTimeout: (t: number) => jest.setTimeout(t),
|
||||
settings: {
|
||||
|
@ -109,7 +113,7 @@ export const getEsClient = async ({
|
|||
|
||||
export const getKibanaMigratorTestKit = async ({
|
||||
settings = {},
|
||||
kibanaIndex = '.kibana',
|
||||
kibanaIndex = defaultKibanaIndex,
|
||||
kibanaVersion = currentVersion,
|
||||
kibanaBranch = currentBranch,
|
||||
types = [],
|
||||
|
@ -122,7 +126,6 @@ export const getKibanaMigratorTestKit = async ({
|
|||
|
||||
// configure logging system
|
||||
const loggingConf = await firstValueFrom(configService.atPath<LoggingConfigType>('logging'));
|
||||
loggingSystem.upgrade(loggingConf);
|
||||
|
||||
const client = await getElasticsearchClient(configService, loggerFactory, kibanaVersion);
|
||||
|
||||
|
@ -142,8 +145,11 @@ export const getKibanaMigratorTestKit = async ({
|
|||
);
|
||||
|
||||
const runMigrations = async (rerun?: boolean) => {
|
||||
loggingSystem.upgrade(loggingConf);
|
||||
migrator.prepareMigrations();
|
||||
return await migrator.runMigrations({ rerun });
|
||||
const migrationResults = await migrator.runMigrations({ rerun });
|
||||
await loggingSystem.stop();
|
||||
return migrationResults;
|
||||
};
|
||||
|
||||
const savedObjectsRepository = SavedObjectsRepository.createRepository(
|
||||
|
@ -156,7 +162,6 @@ export const getKibanaMigratorTestKit = async ({
|
|||
|
||||
return {
|
||||
client,
|
||||
migrator,
|
||||
runMigrations,
|
||||
typeRegistry,
|
||||
savedObjectsRepository,
|
||||
|
@ -272,3 +277,112 @@ const registerTypes = (
|
|||
) => {
|
||||
(types || []).forEach((type) => typeRegistry.registerType(type));
|
||||
};
|
||||
|
||||
export const createBaseline = async () => {
|
||||
const { client, runMigrations, savedObjectsRepository } = await getKibanaMigratorTestKit({
|
||||
kibanaIndex: defaultKibanaIndex,
|
||||
types: baselineTypes,
|
||||
});
|
||||
|
||||
await runMigrations();
|
||||
|
||||
await savedObjectsRepository.bulkCreate(baselineDocuments, {
|
||||
refresh: 'wait_for',
|
||||
});
|
||||
|
||||
return client;
|
||||
};
|
||||
|
||||
interface GetMutatedMigratorParams {
|
||||
kibanaVersion?: string;
|
||||
settings?: Record<string, any>;
|
||||
}
|
||||
|
||||
export const getIdenticalMappingsMigrator = async ({
|
||||
kibanaVersion = nextMinor,
|
||||
settings = {},
|
||||
}: GetMutatedMigratorParams = {}) => {
|
||||
return await getKibanaMigratorTestKit({
|
||||
types: baselineTypes,
|
||||
kibanaVersion,
|
||||
settings,
|
||||
});
|
||||
};
|
||||
|
||||
export const getNonDeprecatedMappingsMigrator = async ({
|
||||
kibanaVersion = nextMinor,
|
||||
settings = {},
|
||||
}: GetMutatedMigratorParams = {}) => {
|
||||
return await getKibanaMigratorTestKit({
|
||||
types: baselineTypes.filter((type) => type.name !== 'deprecated'),
|
||||
kibanaVersion,
|
||||
settings,
|
||||
});
|
||||
};
|
||||
|
||||
export const getCompatibleMappingsMigrator = async ({
|
||||
filterDeprecated = false,
|
||||
kibanaVersion = nextMinor,
|
||||
settings = {},
|
||||
}: GetMutatedMigratorParams & { filterDeprecated?: boolean } = {}) => {
|
||||
const types = baselineTypes
|
||||
.filter((type) => !filterDeprecated || type.name !== 'deprecated')
|
||||
.map<SavedObjectsType>((type) => {
|
||||
if (type.name === 'complex') {
|
||||
return {
|
||||
...type,
|
||||
mappings: {
|
||||
properties: {
|
||||
name: { type: 'text' },
|
||||
value: { type: 'integer' },
|
||||
createdAt: { type: 'date' },
|
||||
},
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return type;
|
||||
}
|
||||
});
|
||||
|
||||
return await getKibanaMigratorTestKit({
|
||||
types,
|
||||
kibanaVersion,
|
||||
settings,
|
||||
});
|
||||
};
|
||||
|
||||
export const getIncompatibleMappingsMigrator = async ({
|
||||
kibanaVersion = nextMinor,
|
||||
settings = {},
|
||||
}: GetMutatedMigratorParams = {}) => {
|
||||
const types = baselineTypes.map<SavedObjectsType>((type) => {
|
||||
if (type.name === 'complex') {
|
||||
return {
|
||||
...type,
|
||||
mappings: {
|
||||
properties: {
|
||||
name: { type: 'keyword' },
|
||||
value: { type: 'long' },
|
||||
createdAt: { type: 'date' },
|
||||
},
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return type;
|
||||
}
|
||||
});
|
||||
|
||||
return await getKibanaMigratorTestKit({
|
||||
types,
|
||||
kibanaVersion,
|
||||
settings,
|
||||
});
|
||||
};
|
||||
|
||||
export const readLog = async (logFilePath: string = defaultLogFilePath): Promise<string> => {
|
||||
return await fs.readFile(logFilePath, 'utf-8');
|
||||
};
|
||||
|
||||
export const clearLog = async (logFilePath: string = defaultLogFilePath): Promise<void> => {
|
||||
await fs.truncate(logFilePath).catch(() => {});
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue