mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
# Backport This will backport the following commits from `main` to `8.16`: - [Update mappings if/when new SO types are introduced (#197061)](https://github.com/elastic/kibana/pull/197061) <!--- Backport version: 9.4.3 --> ### 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":"2024-10-24T08:21:43Z","message":"Update mappings if/when new SO types are introduced (#197061)\n\n## Summary\r\n\r\nAddresses https://github.com/elastic/elastic-entity-model/issues/70\r\nFixes regression introduced in\r\nhttps://github.com/elastic/kibana/pull/176803","sha":"8de3636e43be7c874b2c3457f1496a0fc31f224d","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","Team:Core","release_note:skip","v9.0.0","backport:prev-minor","backport:prev-major","v8.16.0","v8.15.3"],"title":"Update mappings if/when new SO types are introduced","number":197061,"url":"https://github.com/elastic/kibana/pull/197061","mergeCommit":{"message":"Update mappings if/when new SO types are introduced (#197061)\n\n## Summary\r\n\r\nAddresses https://github.com/elastic/elastic-entity-model/issues/70\r\nFixes regression introduced in\r\nhttps://github.com/elastic/kibana/pull/176803","sha":"8de3636e43be7c874b2c3457f1496a0fc31f224d"}},"sourceBranch":"main","suggestedTargetBranches":["8.16","8.15"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/197061","number":197061,"mergeCommit":{"message":"Update mappings if/when new SO types are introduced (#197061)\n\n## Summary\r\n\r\nAddresses https://github.com/elastic/elastic-entity-model/issues/70\r\nFixes regression introduced in\r\nhttps://github.com/elastic/kibana/pull/176803","sha":"8de3636e43be7c874b2c3457f1496a0fc31f224d"}},{"branch":"8.16","label":"v8.16.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.15","label":"v8.15.3","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Gerard Soldevila <gerard.soldevila@elastic.co>
This commit is contained in:
parent
6a58931276
commit
3687435cb4
13 changed files with 217 additions and 102 deletions
|
@ -176,8 +176,9 @@ describe('checkTargetTypesMappings', () => {
|
|||
|
||||
const result = await task();
|
||||
expect(result).toEqual(
|
||||
Either.right({
|
||||
type: 'types_match' as const,
|
||||
Either.left({
|
||||
type: 'types_added' as const,
|
||||
newTypes: ['type3'],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
|
|
@ -11,7 +11,7 @@ import * as Either from 'fp-ts/lib/Either';
|
|||
import * as TaskEither from 'fp-ts/lib/TaskEither';
|
||||
|
||||
import type { IndexMapping, VirtualVersionMap } from '@kbn/core-saved-objects-base-server-internal';
|
||||
import { getUpdatedTypes } from '../core/compare_mappings';
|
||||
import { getNewAndUpdatedTypes } from '../core/compare_mappings';
|
||||
|
||||
/** @internal */
|
||||
export interface CheckTargetTypesMappingsParams {
|
||||
|
@ -38,6 +38,12 @@ export interface TypesChanged {
|
|||
updatedTypes: string[];
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface TypesAdded {
|
||||
type: 'types_added';
|
||||
newTypes: string[];
|
||||
}
|
||||
|
||||
export const checkTargetTypesMappings =
|
||||
({
|
||||
indexTypes,
|
||||
|
@ -46,7 +52,7 @@ export const checkTargetTypesMappings =
|
|||
latestMappingsVersions,
|
||||
hashToVersionMap = {},
|
||||
}: CheckTargetTypesMappingsParams): TaskEither.TaskEither<
|
||||
IndexMappingsIncomplete | TypesChanged,
|
||||
IndexMappingsIncomplete | TypesChanged | TypesAdded,
|
||||
TypesMatch
|
||||
> =>
|
||||
async () => {
|
||||
|
@ -58,7 +64,7 @@ export const checkTargetTypesMappings =
|
|||
return Either.left({ type: 'index_mappings_incomplete' as const });
|
||||
}
|
||||
|
||||
const updatedTypes = getUpdatedTypes({
|
||||
const { newTypes, updatedTypes } = getNewAndUpdatedTypes({
|
||||
indexTypes,
|
||||
indexMeta: indexMappings?._meta,
|
||||
latestMappingsVersions,
|
||||
|
@ -70,6 +76,11 @@ export const checkTargetTypesMappings =
|
|||
type: 'types_changed' as const,
|
||||
updatedTypes,
|
||||
});
|
||||
} else if (newTypes.length) {
|
||||
return Either.left({
|
||||
type: 'types_added' as const,
|
||||
newTypes,
|
||||
});
|
||||
} else {
|
||||
return Either.right({ type: 'types_match' as const });
|
||||
}
|
||||
|
|
|
@ -112,7 +112,7 @@ import type { UnknownDocsFound } from './check_for_unknown_docs';
|
|||
import type { IncompatibleClusterRoutingAllocation } from './check_cluster_routing_allocation';
|
||||
import type { ClusterShardLimitExceeded } from './create_index';
|
||||
import type { SynchronizationFailed } from './synchronize_migrators';
|
||||
import type { IndexMappingsIncomplete, TypesChanged } from './check_target_mappings';
|
||||
import type { IndexMappingsIncomplete, TypesAdded, TypesChanged } from './check_target_mappings';
|
||||
|
||||
export type {
|
||||
CheckForUnknownDocsParams,
|
||||
|
@ -193,6 +193,7 @@ export interface ActionErrorTypeMap {
|
|||
synchronization_failed: SynchronizationFailed;
|
||||
index_mappings_incomplete: IndexMappingsIncomplete;
|
||||
types_changed: TypesChanged;
|
||||
types_added: TypesAdded;
|
||||
operation_not_supported: OperationNotSupported;
|
||||
source_equals_target: SourceEqualsTarget;
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ describe('updateSourceMappingsProperties', () => {
|
|||
appMappings: {
|
||||
properties: {
|
||||
a: { type: 'keyword' },
|
||||
b: { type: 'long' },
|
||||
c: { type: 'long' },
|
||||
...getBaseMappings().properties,
|
||||
},
|
||||
|
@ -66,6 +67,21 @@ describe('updateSourceMappingsProperties', () => {
|
|||
});
|
||||
|
||||
it('should not update mappings when there are no changes', async () => {
|
||||
// we overwrite the app mappings to have the "unchanged" values with respect to the index mappings
|
||||
const sameMappingsParams = chain(params)
|
||||
// let's not introduce 'c' for now
|
||||
.set('indexTypes', ['a', 'b'])
|
||||
// even if the app versions are more recent, we emulate a scenario where mappings haven NOT changed
|
||||
.set('latestMappingsVersions', { a: '10.1.0', b: '10.1.0' })
|
||||
.value();
|
||||
const result = await updateSourceMappingsProperties(sameMappingsParams)();
|
||||
|
||||
expect(client.indices.putMapping).not.toHaveBeenCalled();
|
||||
expect(Either.isRight(result)).toEqual(true);
|
||||
expect(result).toHaveProperty('right', 'update_mappings_succeeded');
|
||||
});
|
||||
|
||||
it('should update mappings if there are new types', async () => {
|
||||
// we overwrite the app mappings to have the "unchanged" values with respect to the index mappings
|
||||
const sameMappingsParams = chain(params)
|
||||
// even if the app versions are more recent, we emulate a scenario where mappings haven NOT changed
|
||||
|
@ -73,7 +89,16 @@ describe('updateSourceMappingsProperties', () => {
|
|||
.value();
|
||||
const result = await updateSourceMappingsProperties(sameMappingsParams)();
|
||||
|
||||
expect(client.indices.putMapping).not.toHaveBeenCalled();
|
||||
expect(client.indices.putMapping).toHaveBeenCalledTimes(1);
|
||||
expect(client.indices.putMapping).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
properties: expect.objectContaining({
|
||||
a: { type: 'keyword' },
|
||||
b: { type: 'long' },
|
||||
c: { type: 'long' },
|
||||
}),
|
||||
})
|
||||
);
|
||||
expect(Either.isRight(result)).toEqual(true);
|
||||
expect(result).toHaveProperty('right', 'update_mappings_succeeded');
|
||||
});
|
||||
|
|
|
@ -9,17 +9,20 @@
|
|||
|
||||
import type { IndexMappingMeta } from '@kbn/core-saved-objects-base-server-internal';
|
||||
import { getBaseMappings } from './build_active_mappings';
|
||||
import { getUpdatedTypes, getUpdatedRootFields } from './compare_mappings';
|
||||
import { getUpdatedRootFields, getNewAndUpdatedTypes } from './compare_mappings';
|
||||
|
||||
describe('getUpdatedTypes', () => {
|
||||
describe('getNewAndUpdatedTypes', () => {
|
||||
test('returns all types if _meta is missing in indexMappings', () => {
|
||||
const indexTypes = ['foo', 'bar'];
|
||||
const latestMappingsVersions = {};
|
||||
|
||||
expect(getUpdatedTypes({ indexTypes, indexMeta: undefined, latestMappingsVersions })).toEqual([
|
||||
'foo',
|
||||
'bar',
|
||||
]);
|
||||
const { newTypes, updatedTypes } = getNewAndUpdatedTypes({
|
||||
indexTypes,
|
||||
indexMeta: undefined,
|
||||
latestMappingsVersions,
|
||||
});
|
||||
expect(newTypes).toEqual([]);
|
||||
expect(updatedTypes).toEqual(['foo', 'bar']);
|
||||
});
|
||||
|
||||
test('returns all types if migrationMappingPropertyHashes and mappingVersions are missing in indexMappings', () => {
|
||||
|
@ -27,14 +30,17 @@ describe('getUpdatedTypes', () => {
|
|||
const indexMeta: IndexMappingMeta = {};
|
||||
const latestMappingsVersions = {};
|
||||
|
||||
expect(getUpdatedTypes({ indexTypes, indexMeta, latestMappingsVersions })).toEqual([
|
||||
'foo',
|
||||
'bar',
|
||||
]);
|
||||
const { newTypes, updatedTypes } = getNewAndUpdatedTypes({
|
||||
indexTypes,
|
||||
indexMeta,
|
||||
latestMappingsVersions,
|
||||
});
|
||||
expect(newTypes).toEqual([]);
|
||||
expect(updatedTypes).toEqual(['foo', 'bar']);
|
||||
});
|
||||
|
||||
describe('when ONLY migrationMappingPropertyHashes exists in indexMappings', () => {
|
||||
test('uses the provided hashToVersionMap to compare changes and return only the types that have changed', async () => {
|
||||
test('uses the provided hashToVersionMap to compare changes and return new types and types that have changed', async () => {
|
||||
const indexTypes = ['type1', 'type2', 'type4'];
|
||||
const indexMeta: IndexMappingMeta = {
|
||||
migrationMappingPropertyHashes: {
|
||||
|
@ -56,14 +62,19 @@ describe('getUpdatedTypes', () => {
|
|||
type4: '10.5.0', // new type, no need to pick it up
|
||||
};
|
||||
|
||||
expect(
|
||||
getUpdatedTypes({ indexTypes, indexMeta, latestMappingsVersions, hashToVersionMap })
|
||||
).toEqual(['type2']);
|
||||
const { newTypes, updatedTypes } = getNewAndUpdatedTypes({
|
||||
indexTypes,
|
||||
indexMeta,
|
||||
latestMappingsVersions,
|
||||
hashToVersionMap,
|
||||
});
|
||||
expect(newTypes).toEqual(['type4']);
|
||||
expect(updatedTypes).toEqual(['type2']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when mappingVersions exist in indexMappings', () => {
|
||||
test('compares the modelVersions and returns only the types that have changed', async () => {
|
||||
test('compares the modelVersions and returns new types and types that have changed', async () => {
|
||||
const indexTypes = ['type1', 'type2', 'type4'];
|
||||
|
||||
const indexMeta: IndexMappingMeta = {
|
||||
|
@ -90,9 +101,14 @@ describe('getUpdatedTypes', () => {
|
|||
// empty on purpose, not used as mappingVersions is present in indexMappings
|
||||
};
|
||||
|
||||
expect(
|
||||
getUpdatedTypes({ indexTypes, indexMeta, latestMappingsVersions, hashToVersionMap })
|
||||
).toEqual(['type2']);
|
||||
const { newTypes, updatedTypes } = getNewAndUpdatedTypes({
|
||||
indexTypes,
|
||||
indexMeta,
|
||||
latestMappingsVersions,
|
||||
hashToVersionMap,
|
||||
});
|
||||
expect(newTypes).toEqual(['type4']);
|
||||
expect(updatedTypes).toEqual(['type2']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -33,43 +33,56 @@ export const getUpdatedRootFields = (indexMappings: IndexMapping): string[] => {
|
|||
.map(([propertyName]) => propertyName);
|
||||
};
|
||||
|
||||
/**
|
||||
* Compares the current vs stored mappings' hashes or modelVersions.
|
||||
* Returns a list with all the types that have been updated.
|
||||
* @param indexMeta The meta information stored in the SO index
|
||||
* @param knownTypes The list of SO types that belong to the index and are enabled
|
||||
* @param latestMappingsVersions A map holding [type => version] with the latest versions where mappings have changed for each type
|
||||
* @param hashToVersionMap A map holding information about [md5 => modelVersion] equivalence
|
||||
* @returns the list of types that have been updated (in terms of their mappings)
|
||||
*/
|
||||
export const getUpdatedTypes = ({
|
||||
indexMeta,
|
||||
indexTypes,
|
||||
latestMappingsVersions,
|
||||
hashToVersionMap = {},
|
||||
}: {
|
||||
interface GetUpdatedTypesParams {
|
||||
indexMeta?: IndexMappingMeta;
|
||||
indexTypes: string[];
|
||||
latestMappingsVersions: VirtualVersionMap;
|
||||
hashToVersionMap?: Record<string, string>;
|
||||
}): string[] => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the current vs stored mappings' hashes or modelVersions.
|
||||
* Returns 2 lists: one with all the new types and one with the types that have been updated.
|
||||
* @param indexMeta The meta information stored in the SO index
|
||||
* @param knownTypes The list of SO types that belong to the index and are enabled
|
||||
* @param latestMappingsVersions A map holding [type => version] with the latest versions where mappings have changed for each type
|
||||
* @param hashToVersionMap A map holding information about [md5 => modelVersion] equivalence
|
||||
* @returns the lists of new types and updated types
|
||||
*/
|
||||
export const getNewAndUpdatedTypes = ({
|
||||
indexMeta,
|
||||
indexTypes,
|
||||
latestMappingsVersions,
|
||||
hashToVersionMap = {},
|
||||
}: GetUpdatedTypesParams) => {
|
||||
if (!indexMeta || (!indexMeta.mappingVersions && !indexMeta.migrationMappingPropertyHashes)) {
|
||||
// if we currently do NOT have meta information stored in the index
|
||||
// we consider that all types have been updated
|
||||
return indexTypes;
|
||||
return { newTypes: [], updatedTypes: indexTypes };
|
||||
}
|
||||
|
||||
// If something exists in stored, but is missing in current
|
||||
// we don't care, as it could be a disabled plugin, etc
|
||||
// and keeping stale stuff around is better than migrating unecessesarily.
|
||||
return indexTypes.filter((type) =>
|
||||
isTypeUpdated({
|
||||
const newTypes: string[] = [];
|
||||
const updatedTypes: string[] = [];
|
||||
|
||||
indexTypes.forEach((type) => {
|
||||
const status = checkTypeStatus({
|
||||
type,
|
||||
mappingVersion: latestMappingsVersions[type],
|
||||
indexMeta,
|
||||
hashToVersionMap,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
if (status === 'new') {
|
||||
newTypes.push(type);
|
||||
} else if (status === 'updated') {
|
||||
updatedTypes.push(type);
|
||||
}
|
||||
});
|
||||
|
||||
return { newTypes, updatedTypes };
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -78,9 +91,9 @@ export const getUpdatedTypes = ({
|
|||
* @param mappingVersion The most recent model version that includes mappings changes
|
||||
* @param indexMeta The meta information stored in the SO index
|
||||
* @param hashToVersionMap A map holding information about [md5 => modelVersion] equivalence
|
||||
* @returns true if the mappings for the given type have changed since Kibana was last started
|
||||
* @returns 'new' | 'updated' | 'unchanged' depending on whether the type has changed
|
||||
*/
|
||||
function isTypeUpdated({
|
||||
function checkTypeStatus({
|
||||
type,
|
||||
mappingVersion,
|
||||
indexMeta,
|
||||
|
@ -90,7 +103,7 @@ function isTypeUpdated({
|
|||
mappingVersion: string;
|
||||
indexMeta: IndexMappingMeta;
|
||||
hashToVersionMap: Record<string, string>;
|
||||
}): boolean {
|
||||
}): 'new' | 'updated' | 'unchanged' {
|
||||
const latestMappingsVersion = Semver.parse(mappingVersion);
|
||||
if (!latestMappingsVersion) {
|
||||
throw new Error(
|
||||
|
@ -104,26 +117,28 @@ function isTypeUpdated({
|
|||
if (!indexVersion) {
|
||||
// either a new type, and thus there's not need to update + pickup any docs
|
||||
// or an old re-enabled type, which will be updated on OUTDATED_DOCUMENTS_TRANSFORM
|
||||
return false;
|
||||
return 'new';
|
||||
}
|
||||
|
||||
// if the last version where mappings have changed is more recent than the one stored in the index
|
||||
// it means that the type has been updated
|
||||
return latestMappingsVersion.compare(indexVersion) === 1;
|
||||
return latestMappingsVersion.compare(indexVersion) === 1 ? 'updated' : 'unchanged';
|
||||
} else if (indexMeta.migrationMappingPropertyHashes) {
|
||||
const latestHash = indexMeta.migrationMappingPropertyHashes?.[type];
|
||||
|
||||
if (!latestHash) {
|
||||
// either a new type, and thus there's not need to update + pickup any docs
|
||||
// or an old re-enabled type, which will be updated on OUTDATED_DOCUMENTS_TRANSFORM
|
||||
return false;
|
||||
return 'new';
|
||||
}
|
||||
|
||||
const indexEquivalentVersion = hashToVersionMap[`${type}|${latestHash}`];
|
||||
return !indexEquivalentVersion || latestMappingsVersion.compare(indexEquivalentVersion) === 1;
|
||||
return !indexEquivalentVersion || latestMappingsVersion.compare(indexEquivalentVersion) === 1
|
||||
? 'updated'
|
||||
: 'unchanged';
|
||||
}
|
||||
|
||||
// at this point, the mappings do not contain any meta informataion
|
||||
// we consider the type has been updated, out of caution
|
||||
return true;
|
||||
return 'updated';
|
||||
}
|
||||
|
|
|
@ -9,12 +9,14 @@
|
|||
|
||||
import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal';
|
||||
import { getBaseMappings } from './build_active_mappings';
|
||||
import { getUpdatedRootFields, getUpdatedTypes } from './compare_mappings';
|
||||
import { getUpdatedRootFields, getNewAndUpdatedTypes } from './compare_mappings';
|
||||
import { diffMappings } from './diff_mappings';
|
||||
|
||||
jest.mock('./compare_mappings');
|
||||
const getUpdatedRootFieldsMock = getUpdatedRootFields as jest.MockedFn<typeof getUpdatedRootFields>;
|
||||
const getUpdatedTypesMock = getUpdatedTypes as jest.MockedFn<typeof getUpdatedTypes>;
|
||||
const getNewAndUpdatedTypesMock = getNewAndUpdatedTypes as jest.MockedFn<
|
||||
typeof getNewAndUpdatedTypes
|
||||
>;
|
||||
|
||||
const dummyMappings: IndexMapping = {
|
||||
_meta: {
|
||||
|
@ -56,7 +58,7 @@ const dummyHashToVersionMap = {
|
|||
describe('diffMappings', () => {
|
||||
beforeEach(() => {
|
||||
getUpdatedRootFieldsMock.mockReset();
|
||||
getUpdatedTypesMock.mockReset();
|
||||
getNewAndUpdatedTypesMock.mockReset();
|
||||
});
|
||||
|
||||
test('is different if dynamic is different', () => {
|
||||
|
@ -114,14 +116,17 @@ describe('diffMappings', () => {
|
|||
|
||||
expect(getUpdatedRootFieldsMock).toHaveBeenCalledTimes(1);
|
||||
expect(getUpdatedRootFieldsMock).toHaveBeenCalledWith(initialMappings);
|
||||
expect(getUpdatedTypesMock).not.toHaveBeenCalled();
|
||||
expect(getNewAndUpdatedTypesMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('if some types have changed', () => {
|
||||
describe('if there are new or updated types', () => {
|
||||
test('returns a changed type', () => {
|
||||
getUpdatedRootFieldsMock.mockReturnValueOnce([]);
|
||||
getUpdatedTypesMock.mockReturnValueOnce(['foo', 'bar']);
|
||||
getNewAndUpdatedTypesMock.mockReturnValueOnce({
|
||||
newTypes: ['baz'],
|
||||
updatedTypes: ['foo'],
|
||||
});
|
||||
|
||||
expect(
|
||||
diffMappings({
|
||||
|
@ -137,8 +142,8 @@ describe('diffMappings', () => {
|
|||
|
||||
expect(getUpdatedRootFieldsMock).toHaveBeenCalledTimes(1);
|
||||
expect(getUpdatedRootFieldsMock).toHaveBeenCalledWith(initialMappings);
|
||||
expect(getUpdatedTypesMock).toHaveBeenCalledTimes(1);
|
||||
expect(getUpdatedTypesMock).toHaveBeenCalledWith({
|
||||
expect(getNewAndUpdatedTypesMock).toHaveBeenCalledTimes(1);
|
||||
expect(getNewAndUpdatedTypesMock).toHaveBeenCalledWith({
|
||||
indexTypes: ['foo', 'bar', 'baz'],
|
||||
indexMeta: initialMappings._meta,
|
||||
latestMappingsVersions: {
|
||||
|
@ -152,7 +157,10 @@ describe('diffMappings', () => {
|
|||
describe('if no root field or types have changed', () => {
|
||||
test('returns undefined', () => {
|
||||
getUpdatedRootFieldsMock.mockReturnValueOnce([]);
|
||||
getUpdatedTypesMock.mockReturnValueOnce([]);
|
||||
getNewAndUpdatedTypesMock.mockReturnValueOnce({
|
||||
newTypes: [],
|
||||
updatedTypes: [],
|
||||
});
|
||||
|
||||
expect(
|
||||
diffMappings({
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
*/
|
||||
|
||||
import type { IndexMapping, VirtualVersionMap } from '@kbn/core-saved-objects-base-server-internal';
|
||||
import { getUpdatedRootFields, getUpdatedTypes } from './compare_mappings';
|
||||
import { getNewAndUpdatedTypes, getUpdatedRootFields } from './compare_mappings';
|
||||
|
||||
/**
|
||||
* Diffs the stored vs app mappings.
|
||||
|
@ -56,8 +56,9 @@ export function diffMappings({
|
|||
}
|
||||
|
||||
/**
|
||||
* Finds a property that has changed its schema with respect to the mappings stored in the SO index
|
||||
* It can either be a root field or a SO type
|
||||
* Finds a property (either a root field or a SO type) that either:
|
||||
* - is new (did not exist in the current mappings)
|
||||
* - has changed its schema with respect to the mappings stored in the SO index
|
||||
* @returns the name of the property (if any)
|
||||
*/
|
||||
function findChangedProp({
|
||||
|
@ -76,7 +77,7 @@ function findChangedProp({
|
|||
return updatedFields[0];
|
||||
}
|
||||
|
||||
const updatedTypes = getUpdatedTypes({
|
||||
const { newTypes, updatedTypes } = getNewAndUpdatedTypes({
|
||||
indexMeta: indexMappings._meta,
|
||||
indexTypes,
|
||||
latestMappingsVersions,
|
||||
|
@ -84,6 +85,8 @@ function findChangedProp({
|
|||
});
|
||||
if (updatedTypes.length) {
|
||||
return updatedTypes[0];
|
||||
} else if (newTypes.length) {
|
||||
return newTypes[0];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
|
|
@ -2689,6 +2689,18 @@ describe('migrations v2 model', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS_META if ONLY new SO types have been added', () => {
|
||||
const res: ResponseType<'CHECK_TARGET_MAPPINGS'> = Either.left({
|
||||
type: 'types_added' as const,
|
||||
updatedFields: [],
|
||||
newTypes: ['newFeatureType'],
|
||||
});
|
||||
const newState = model(checkTargetTypesMappingsState, res) as UpdateTargetMappingsMeta;
|
||||
expect(newState.controlState).toEqual('UPDATE_TARGET_MAPPINGS_META');
|
||||
expect(newState.retryCount).toEqual(0);
|
||||
expect(newState.retryDelay).toEqual(0);
|
||||
});
|
||||
|
||||
it('CHECK_TARGET_MAPPINGS -> CHECK_VERSION_INDEX_READY_ACTIONS if types match (there might be additions in core fields)', () => {
|
||||
const res: ResponseType<'CHECK_TARGET_MAPPINGS'> = Either.right({
|
||||
type: 'types_match' as const,
|
||||
|
|
|
@ -1522,6 +1522,12 @@ export const model = (currentState: State, resW: ResponseType<AllActionStates>):
|
|||
},
|
||||
],
|
||||
};
|
||||
} else if (isTypeof(left, 'types_added')) {
|
||||
// compatible migration: ONLY new SO types have been introduced, skip directly to UPDATE_TARGET_MAPPINGS_META
|
||||
return {
|
||||
...stateP,
|
||||
controlState: 'UPDATE_TARGET_MAPPINGS_META',
|
||||
};
|
||||
} else {
|
||||
throwBadResponse(stateP, res as never);
|
||||
}
|
||||
|
|
|
@ -67,34 +67,48 @@ describe('v2 migration', () => {
|
|||
migrationResults = await upToDateKit.runMigrations();
|
||||
});
|
||||
|
||||
it('updates the index mappings to account for new SO types', async () => {
|
||||
const res = await upToDateKit.client.indices.getMapping({ index: defaultKibanaIndex });
|
||||
const mappings = res[`${defaultKibanaIndex}_${currentVersion}_001`].mappings;
|
||||
|
||||
expect(mappings._meta?.indexTypesMap[defaultKibanaIndex]).toContain('recent');
|
||||
expect(mappings.properties?.recent).toEqual({
|
||||
properties: {
|
||||
name: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('skips UPDATE_TARGET_MAPPINGS_PROPERTIES if there are no changes in the mappings', async () => {
|
||||
const logs = await readLog(logFilePath);
|
||||
expect(logs).not.toMatch('CREATE_NEW_TARGET');
|
||||
|
||||
// defaultKibana index has a new SO type ('recent'), thus we must update the _meta properties
|
||||
expect(logs).toMatch(
|
||||
`[${defaultKibanaIndex}] CHECK_TARGET_MAPPINGS -> CHECK_VERSION_INDEX_READY_ACTIONS`
|
||||
`[${defaultKibanaIndex}] CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS_META.`
|
||||
);
|
||||
expect(logs).toMatch(
|
||||
`[${defaultKibanaTaskIndex}] CHECK_TARGET_MAPPINGS -> CHECK_VERSION_INDEX_READY_ACTIONS`
|
||||
);
|
||||
|
||||
// no updated types, so no pickup
|
||||
expect(logs).not.toMatch('UPDATE_TARGET_MAPPINGS_PROPERTIES');
|
||||
expect(logs).not.toMatch('UPDATE_TARGET_MAPPINGS_PROPERTIES_WAIT_FOR_TASK');
|
||||
expect(logs).not.toMatch('UPDATE_TARGET_MAPPINGS_META');
|
||||
});
|
||||
|
||||
it(`returns a 'patched' status for each SO index`, () => {
|
||||
// omit elapsedMs as it varies in each execution
|
||||
expect(migrationResults.map((result) => omit(result, 'elapsedMs'))).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"destIndex": ".kibana_migrator_${currentVersion}_001",
|
||||
"status": "patched",
|
||||
},
|
||||
Object {
|
||||
"destIndex": ".kibana_migrator_tasks_${currentVersion}_001",
|
||||
"status": "patched",
|
||||
},
|
||||
]
|
||||
`);
|
||||
expect(migrationResults.map((result) => omit(result, 'elapsedMs'))).toEqual([
|
||||
{
|
||||
destIndex: `${defaultKibanaIndex}_${currentVersion}_001`,
|
||||
status: 'patched',
|
||||
},
|
||||
{
|
||||
destIndex: `${defaultKibanaTaskIndex}_${currentVersion}_001`,
|
||||
status: 'patched',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('each migrator takes less than 10 seconds', () => {
|
||||
|
@ -318,21 +332,18 @@ describe('v2 migration', () => {
|
|||
|
||||
it('returns a migrated status for each SO index', () => {
|
||||
// omit elapsedMs as it varies in each execution
|
||||
expect(migrationResults.map((result) => omit(result, 'elapsedMs')))
|
||||
.toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"destIndex": ".kibana_migrator_${nextMinor}_001",
|
||||
"sourceIndex": ".kibana_migrator_${currentVersion}_001",
|
||||
"status": "migrated",
|
||||
},
|
||||
Object {
|
||||
"destIndex": ".kibana_migrator_tasks_${currentVersion}_001",
|
||||
"sourceIndex": ".kibana_migrator_tasks_${currentVersion}_001",
|
||||
"status": "migrated",
|
||||
},
|
||||
]
|
||||
`);
|
||||
expect(migrationResults.map((result) => omit(result, 'elapsedMs'))).toEqual([
|
||||
{
|
||||
destIndex: `${defaultKibanaIndex}_${nextMinor}_001`,
|
||||
sourceIndex: `${defaultKibanaIndex}_${currentVersion}_001`,
|
||||
status: 'migrated',
|
||||
},
|
||||
{
|
||||
destIndex: `${defaultKibanaTaskIndex}_${currentVersion}_001`,
|
||||
sourceIndex: `${defaultKibanaTaskIndex}_${currentVersion}_001`,
|
||||
status: 'migrated',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('each migrator takes less than 60 seconds', () => {
|
||||
|
|
|
@ -139,7 +139,7 @@ describe('multiple Kibana nodes performing a reindexing migration', () => {
|
|||
|
||||
const typesMap =
|
||||
indicesInfo[`${defaultKibanaIndex}_${nextMinor}_001`].mappings?._meta?.indexTypesMap;
|
||||
expect(typesMap[defaultKibanaIndex]).toEqual(['complex', 'server']); // 'deprecated' no longer present
|
||||
expect(typesMap[defaultKibanaIndex]).toEqual(['complex', 'recent', 'server']); // 'deprecated' no longer present
|
||||
expect(typesMap[kibanaSplitIndex]).toEqual(['basic', 'task']);
|
||||
}
|
||||
|
||||
|
@ -239,11 +239,11 @@ describe('multiple Kibana nodes performing a reindexing migration', () => {
|
|||
)
|
||||
).toEqual([
|
||||
{
|
||||
destIndex: `.kibana_migrator_${nextMinor}_001`,
|
||||
destIndex: `${defaultKibanaIndex}_${nextMinor}_001`,
|
||||
status: 'patched',
|
||||
},
|
||||
{
|
||||
destIndex: `.kibana_migrator_split_${nextMinor}_001`,
|
||||
destIndex: `${kibanaSplitIndex}_${nextMinor}_001`,
|
||||
status: 'patched',
|
||||
},
|
||||
]);
|
||||
|
|
|
@ -81,8 +81,14 @@ export const baselineTypes: Array<SavedObjectsType<any>> = [
|
|||
},
|
||||
];
|
||||
|
||||
export const getUpToDateBaselineTypes = (filterDeprecated: boolean) =>
|
||||
baselineTypes.filter((type) => !filterDeprecated || type.name !== 'deprecated');
|
||||
export const getUpToDateBaselineTypes = (filterDeprecated: boolean) => [
|
||||
...baselineTypes.filter((type) => !filterDeprecated || type.name !== 'deprecated'),
|
||||
// we add a new SO type
|
||||
{
|
||||
...defaultType,
|
||||
name: 'recent',
|
||||
},
|
||||
];
|
||||
|
||||
export const getCompatibleBaselineTypes = (filterDeprecated: boolean) =>
|
||||
getUpToDateBaselineTypes(filterDeprecated).map<SavedObjectsType>((type) => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue