Log error when encountering corrupt saved object during migration (#65829) (#66013)

* Log error when encountering corrupt saved object during migration

* Fix documentation

* Fix types
This commit is contained in:
Rudolf Meijering 2020-05-11 20:39:26 +02:00 committed by GitHub
parent 4f119a16fd
commit 22136d80e1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 56 additions and 18 deletions

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [SavedObjectsMigrationLogger](./kibana-plugin-core-server.savedobjectsmigrationlogger.md) &gt; [error](./kibana-plugin-core-server.savedobjectsmigrationlogger.error.md)
## SavedObjectsMigrationLogger.error property
<b>Signature:</b>
```typescript
error: (msg: string, meta: LogMeta) => void;
```

View file

@ -16,6 +16,7 @@ export interface SavedObjectsMigrationLogger
| Property | Type | Description | | Property | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| [debug](./kibana-plugin-core-server.savedobjectsmigrationlogger.debug.md) | <code>(msg: string) =&gt; void</code> | | | [debug](./kibana-plugin-core-server.savedobjectsmigrationlogger.debug.md) | <code>(msg: string) =&gt; void</code> | |
| [error](./kibana-plugin-core-server.savedobjectsmigrationlogger.error.md) | <code>(msg: string, meta: LogMeta) =&gt; void</code> | |
| [info](./kibana-plugin-core-server.savedobjectsmigrationlogger.info.md) | <code>(msg: string) =&gt; void</code> | | | [info](./kibana-plugin-core-server.savedobjectsmigrationlogger.info.md) | <code>(msg: string) =&gt; void</code> | |
| [warn](./kibana-plugin-core-server.savedobjectsmigrationlogger.warn.md) | <code>(msg: string) =&gt; void</code> | | | [warn](./kibana-plugin-core-server.savedobjectsmigrationlogger.warn.md) | <code>(msg: string) =&gt; void</code> | |
| [warning](./kibana-plugin-core-server.savedobjectsmigrationlogger.warning.md) | <code>(msg: string) =&gt; void</code> | | | [warning](./kibana-plugin-core-server.savedobjectsmigrationlogger.warning.md) | <code>(msg: string) =&gt; void</code> | |

View file

@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) &gt; [IIndexPattern](./kibana-plugin-plugins-data-server.iindexpattern.md) &gt; [getTimeField](./kibana-plugin-plugins-data-server.iindexpattern.gettimefield.md)
## IIndexPattern.getTimeField() method
<b>Signature:</b>
```typescript
getTimeField?(): IFieldType | undefined;
```
<b>Returns:</b>
`IFieldType | undefined`

View file

@ -195,7 +195,7 @@ async function migrateSourceToDest(context: Context) {
await Index.write( await Index.write(
callCluster, callCluster,
dest.indexName, dest.indexName,
migrateRawDocs(serializer, documentMigrator.migrate, docs) migrateRawDocs(serializer, documentMigrator.migrate, docs, log)
); );
} }
} }

View file

@ -21,6 +21,7 @@ import _ from 'lodash';
import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry'; import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry';
import { SavedObjectsSerializer } from '../../serialization'; import { SavedObjectsSerializer } from '../../serialization';
import { migrateRawDocs } from './migrate_raw_docs'; import { migrateRawDocs } from './migrate_raw_docs';
import { createSavedObjectsMigrationLoggerMock } from '../../migrations/mocks';
describe('migrateRawDocs', () => { describe('migrateRawDocs', () => {
test('converts raw docs to saved objects', async () => { test('converts raw docs to saved objects', async () => {
@ -31,7 +32,8 @@ describe('migrateRawDocs', () => {
[ [
{ _id: 'a:b', _source: { type: 'a', a: { name: 'AAA' } } }, { _id: 'a:b', _source: { type: 'a', a: { name: 'AAA' } } },
{ _id: 'c:d', _source: { type: 'c', c: { name: 'DDD' } } }, { _id: 'c:d', _source: { type: 'c', c: { name: 'DDD' } } },
] ],
createSavedObjectsMigrationLoggerMock()
); );
expect(result).toEqual([ expect(result).toEqual([
@ -48,7 +50,8 @@ describe('migrateRawDocs', () => {
expect(transform).toHaveBeenCalled(); expect(transform).toHaveBeenCalled();
}); });
test('passes invalid docs through untouched', async () => { test('passes invalid docs through untouched and logs error', async () => {
const logger = createSavedObjectsMigrationLoggerMock();
const transform = jest.fn<any, any>((doc: any) => const transform = jest.fn<any, any>((doc: any) =>
_.set(_.cloneDeep(doc), 'attributes.name', 'TADA') _.set(_.cloneDeep(doc), 'attributes.name', 'TADA')
); );
@ -58,7 +61,8 @@ describe('migrateRawDocs', () => {
[ [
{ _id: 'foo:b', _source: { type: 'a', a: { name: 'AAA' } } }, { _id: 'foo:b', _source: { type: 'a', a: { name: 'AAA' } } },
{ _id: 'c:d', _source: { type: 'c', c: { name: 'DDD' } } }, { _id: 'c:d', _source: { type: 'c', c: { name: 'DDD' } } },
] ],
logger
); );
expect(result).toEqual([ expect(result).toEqual([
@ -82,5 +86,7 @@ describe('migrateRawDocs', () => {
}, },
], ],
]); ]);
expect(logger.error).toBeCalledTimes(1);
}); });
}); });

View file

@ -23,6 +23,7 @@
import { SavedObjectsRawDoc, SavedObjectsSerializer } from '../../serialization'; import { SavedObjectsRawDoc, SavedObjectsSerializer } from '../../serialization';
import { TransformFn } from './document_migrator'; import { TransformFn } from './document_migrator';
import { SavedObjectsMigrationLogger } from '.';
/** /**
* Applies the specified migration function to every saved object document in the list * Applies the specified migration function to every saved object document in the list
@ -35,7 +36,8 @@ import { TransformFn } from './document_migrator';
export function migrateRawDocs( export function migrateRawDocs(
serializer: SavedObjectsSerializer, serializer: SavedObjectsSerializer,
migrateDoc: TransformFn, migrateDoc: TransformFn,
rawDocs: SavedObjectsRawDoc[] rawDocs: SavedObjectsRawDoc[],
log: SavedObjectsMigrationLogger
): SavedObjectsRawDoc[] { ): SavedObjectsRawDoc[] {
return rawDocs.map(raw => { return rawDocs.map(raw => {
if (serializer.isRawSavedObject(raw)) { if (serializer.isRawSavedObject(raw)) {
@ -47,6 +49,10 @@ export function migrateRawDocs(
}); });
} }
log.error(
`Error: Unable to migrate the corrupt Saved Object document ${raw._id}. To prevent Kibana from performing a migration on every restart, please delete or fix this document by ensuring that the namespace and type in the document's id matches the values in the namespace and type fields.`,
{ rawDocument: raw }
);
return raw; return raw;
}); });
} }

View file

@ -19,14 +19,10 @@
import _ from 'lodash'; import _ from 'lodash';
import { coordinateMigration } from './migration_coordinator'; import { coordinateMigration } from './migration_coordinator';
import { createSavedObjectsMigrationLoggerMock } from '../mocks';
describe('coordinateMigration', () => { describe('coordinateMigration', () => {
const log = { const log = createSavedObjectsMigrationLoggerMock();
debug: jest.fn(),
warning: jest.fn(),
warn: jest.fn(),
info: jest.fn(),
};
test('waits for isMigrated, if there is an index conflict', async () => { test('waits for isMigrated, if there is an index conflict', async () => {
const pollInterval = 1; const pollInterval = 1;

View file

@ -17,7 +17,7 @@
* under the License. * under the License.
*/ */
import { Logger } from 'src/core/server/logging'; import { Logger, LogMeta } from '../../../logging';
/* /*
* This file provides a helper class for ensuring that all logging * This file provides a helper class for ensuring that all logging
@ -35,6 +35,7 @@ export interface SavedObjectsMigrationLogger {
*/ */
warning: (msg: string) => void; warning: (msg: string) => void;
warn: (msg: string) => void; warn: (msg: string) => void;
error: (msg: string, meta: LogMeta) => void;
} }
export class MigrationLogger implements SavedObjectsMigrationLogger { export class MigrationLogger implements SavedObjectsMigrationLogger {
@ -48,4 +49,5 @@ export class MigrationLogger implements SavedObjectsMigrationLogger {
public debug = (msg: string) => this.logger.debug(msg); public debug = (msg: string) => this.logger.debug(msg);
public warning = (msg: string) => this.logger.warn(msg); public warning = (msg: string) => this.logger.warn(msg);
public warn = (msg: string) => this.logger.warn(msg); public warn = (msg: string) => this.logger.warn(msg);
public error = (msg: string, meta: LogMeta) => this.logger.error(msg, meta);
} }

View file

@ -22,9 +22,9 @@
* (the shape of the mappings and documents in the index). * (the shape of the mappings and documents in the index).
*/ */
import { Logger } from 'src/core/server/logging';
import { KibanaConfigType } from 'src/core/server/kibana_config'; import { KibanaConfigType } from 'src/core/server/kibana_config';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { Logger } from '../../../logging';
import { IndexMapping, SavedObjectsTypeMappingDefinitions } from '../../mappings'; import { IndexMapping, SavedObjectsTypeMappingDefinitions } from '../../mappings';
import { SavedObjectUnsanitizedDoc, SavedObjectsSerializer } from '../../serialization'; import { SavedObjectUnsanitizedDoc, SavedObjectsSerializer } from '../../serialization';
import { docValidator, PropertyValidators } from '../../validation'; import { docValidator, PropertyValidators } from '../../validation';

View file

@ -20,12 +20,13 @@
import { SavedObjectMigrationContext } from './types'; import { SavedObjectMigrationContext } from './types';
import { SavedObjectsMigrationLogger } from './core'; import { SavedObjectsMigrationLogger } from './core';
const createLoggerMock = (): jest.Mocked<SavedObjectsMigrationLogger> => { export const createSavedObjectsMigrationLoggerMock = (): jest.Mocked<SavedObjectsMigrationLogger> => {
const mock = { const mock = {
debug: jest.fn(), debug: jest.fn(),
info: jest.fn(), info: jest.fn(),
warning: jest.fn(), warning: jest.fn(),
warn: jest.fn(), warn: jest.fn(),
error: jest.fn(),
}; };
return mock; return mock;
@ -33,7 +34,7 @@ const createLoggerMock = (): jest.Mocked<SavedObjectsMigrationLogger> => {
const createContextMock = (): jest.Mocked<SavedObjectMigrationContext> => { const createContextMock = (): jest.Mocked<SavedObjectMigrationContext> => {
const mock = { const mock = {
log: createLoggerMock(), log: createSavedObjectsMigrationLoggerMock(),
}; };
return mock; return mock;
}; };

View file

@ -91,7 +91,6 @@ import { IngestGetPipelineParams } from 'elasticsearch';
import { IngestPutPipelineParams } from 'elasticsearch'; import { IngestPutPipelineParams } from 'elasticsearch';
import { IngestSimulateParams } from 'elasticsearch'; import { IngestSimulateParams } from 'elasticsearch';
import { KibanaConfigType } from 'src/core/server/kibana_config'; import { KibanaConfigType } from 'src/core/server/kibana_config';
import { Logger as Logger_2 } from 'src/core/server/logging';
import { MGetParams } from 'elasticsearch'; import { MGetParams } from 'elasticsearch';
import { MGetResponse } from 'elasticsearch'; import { MGetResponse } from 'elasticsearch';
import { MSearchParams } from 'elasticsearch'; import { MSearchParams } from 'elasticsearch';
@ -2170,6 +2169,8 @@ export interface SavedObjectsMigrationLogger {
// (undocumented) // (undocumented)
debug: (msg: string) => void; debug: (msg: string) => void;
// (undocumented) // (undocumented)
error: (msg: string, meta: LogMeta) => void;
// (undocumented)
info: (msg: string) => void; info: (msg: string) => void;
// (undocumented) // (undocumented)
warn: (msg: string) => void; warn: (msg: string) => void;

View file

@ -93,8 +93,7 @@ import { IngestGetPipelineParams } from 'elasticsearch';
import { IngestPutPipelineParams } from 'elasticsearch'; import { IngestPutPipelineParams } from 'elasticsearch';
import { IngestSimulateParams } from 'elasticsearch'; import { IngestSimulateParams } from 'elasticsearch';
import { KibanaConfigType as KibanaConfigType_2 } from 'src/core/server/kibana_config'; import { KibanaConfigType as KibanaConfigType_2 } from 'src/core/server/kibana_config';
import { Logger as Logger_2 } from 'src/core/server/logging'; import { Logger as Logger_2 } from 'kibana/server';
import { Logger as Logger_3 } from 'kibana/server';
import { MGetParams } from 'elasticsearch'; import { MGetParams } from 'elasticsearch';
import { MGetResponse } from 'elasticsearch'; import { MGetResponse } from 'elasticsearch';
import moment from 'moment'; import moment from 'moment';