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

DocumentMigration: implement downward transformation of documents #153921

Merged
merged 17 commits into from
Apr 3, 2023

Conversation

pgayvallet
Copy link
Contributor

@pgayvallet pgayvallet commented Mar 29, 2023

Summary

Fix #150301

Implements DocumentMigrator.transformDown and the associated transformation pipeline.

Note: The feature is currently unused, and will only be when we'll start #150312

@pgayvallet pgayvallet added Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc Feature:Migrations release_note:skip Skip the PR/issue when compiling release notes labels Mar 29, 2023
Copy link
Contributor Author

@pgayvallet pgayvallet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self-review

import { TransformSavedObjectDocumentError } from '.';

type MigrateAndConvertFn = VersionedTransformer['migrateAndConvert'];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the only place where this function type was used, so I inlined it in VersionedTransformer instead

@@ -1329,6 +1342,175 @@ describe('DocumentMigrator', () => {
});
});
});

describe('down transformation', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the unit tests are directly for downgrade_pipeline. In theory, I should have stubbed the pipeline for the doc migrator tests, but

  1. we're not doing it for other unit tests in the file and the 'normal' pipeline
  2. this part is so touchy that including the underlying implementation in the testing makes some sense

So I added some 'integration' smoke tests here.

Comment on lines -9 to -13
/*
* This file contains logic for transforming / migrating a saved object document.
*
* At first, it may seem as if this could be a simple filter + reduce operation,
* running the document through a linear set of transform functions until it is
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This long wall of text is mostly outdated, and doesn't really bring anything, so I decided to finally get rid of it.

Speak now or forever hold your peace.

Comment on lines -74 to -76
migrationVersion: SavedObjectsMigrationVersion;
migrate: MigrateFn;
migrateAndConvert: MigrateAndConvertFn;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VersionedTransformer was 'leaking' migrationVersion and prepareMigrations that shouldn't be present on that kind of publish-ish interfaces, so I cleaned that up

Comment on lines +47 to +50
transformDown: (
doc: SavedObjectUnsanitizedDoc,
options: { targetTypeVersion: string }
) => SavedObjectUnsanitizedDoc;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: I highly suspect that given what we're intending to use the feature for, we will need some kind of getAtVersion API that either convert up or down depending on the doc and target version. However it will likely just delegates to the existing APIs, and I didn't want to implement it before having a better understanding of how the SOR will use the feature in #150312

import type { MigrationPipeline, MigrationPipelineResult } from './types';
import { applyVersion, assertValidCoreVersion } from './utils';

export class DocumentDowngradePipeline implements MigrationPipeline {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I spent a lot of time trying to figure out if it was better to have the logic for both up and down conversions at the same place or them being dissociated, and at the end, I think this is better:

  • The up migration has a lot of legacy logic because of the Reference and Converting migrations that down migrations don't have to care about
  • The logic and sanity check are fully different (e.g assertUpgrade in the up pipeline)
  • Even the look and version-applying logic are somewhat different

So I went with the 2 distinct logics approach, and I still think that's the correct decision.

Comment on lines +86 to +92
case TransformType.Core:
return (
this.targetCoreVersion &&
(coreMigrationVersion == null ||
Semver.gt(coreMigrationVersion, this.targetCoreVersion)) &&
Semver.gt(version, this.targetCoreVersion)
);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I implemented support for downward core transforms, even if it's not planned to use it atm.

Comment on lines +133 to +134
typeMigrationVersion: this.targetTypeVersion,
...(coreMigrationVersion ? { coreMigrationVersion } : {}),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Different behavior than the up migrations, here we always apply/set the target version to the document before returning it.

migrations: ActiveMigrations;
kibanaVersion: string;
convertNamespaceTypes: boolean;
targetTypeVersion?: string;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the targetTypeVersion option to the up pipeline, as I suspect we will need it. Note that atm, the option is not exposed or used by the document migrator itself.

Comment on lines +61 to +62
if (this.document.type !== this.originalDoc.type) {
throw new Error(`Changing a document's type during a migration is not supported.`);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is (finally) no longer possible, as it would be a real problem for ZDT.

Note that no one ever used this functionality anyway.

@pgayvallet pgayvallet marked this pull request as ready for review March 30, 2023 10:20
@pgayvallet pgayvallet requested a review from a team as a code owner March 30, 2023 10:20
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-core (Team:Core)

Copy link
Member

@afharo afharo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job! LGTM! I added a few nits for your consideration.


beforeEach(() => {
const migrate1 = createTransformFn((doc) => {
doc.attributes.wentThoughDown1 = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: wentThroughDown1 (missing r)

1: {
modelChange: {
type: 'expansion',
transformation: { up: jest.fn(), down: migrate1 },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: Are we planning to add an FTR that checks up & down migrations are symmetric? I guess this is something we'll do in follow-up PRs, right?

P.S.: This comment doesn't belong here... but this line reminded me that...

Comment on lines +254 to +257
expect(result).toEqual({
transformedDoc: resultDoc,
additionalDocs: [],
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: should we check that downTransform has been called once?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's covered in the previous test

Copy link
Contributor

@jloleysens jloleysens left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job @pgayvallet ! Left a few comments (mostly for my education).

Note: The feature is currently unused, and will only be when we'll start #150312

👍🏻

Comment on lines +506 to +508
id: 'smelly',
type: 'dog',
attributes: { name: 'Callie' },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Smelly Callie 😄

kibanaVersion: this.options.kibanaVersion,
targetTypeVersion: options.targetTypeVersion,
});
const { document } = pipeline.run();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible for a down migration to create additional docs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In practice, no as the higher-level API does not allow it. I've been reusing the same internal types that the up migration for simplicity's sake.

return transforms;
return Object.entries(modelVersionMap).map<Transform>(([rawModelVersion, definition]) => {
const modelVersion = assertValidModelVersion(rawModelVersion);
const virtualVersion = modelVersionToVirtualVersion(modelVersion);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A little late to the party on this, but would it be possible to get a little bit more context on the exact purpose of a virtual version in the doc comment of modelVersionToVirtualVersion or modelVersionVirtualMajor? I understand what it is (thanks to your examples) but I don't fully understand the role it plays, specifically the "virtual" part.

Copy link
Contributor Author

@pgayvallet pgayvallet Apr 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid breaking change in the way

  1. the documentMigration handles versions
  2. the way versions are stored in typeModelVersion in documents

We're considering that model versions are just version 10. E.g MV 1 = version 10.1.0. This is just a trick to avoid greatly reduce the internal changes

const latestVersion = this.typeTransforms.latestVersion.migrate;

if (currentVersion && Semver.gt(currentVersion, latestVersion)) {
throw Boom.badData(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious: is the intention to continue using/proliferating use of Boom? My understanding is that it is legacy, but that may be mistaken.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to get rid of those atrocities. I just didn't want to do it in the current PR as I suspect some snapshots are based on those. But you're right, for the new pipeline, I can remove them right now. Will do.

@pgayvallet pgayvallet enabled auto-merge (squash) April 3, 2023 05:50
@pgayvallet
Copy link
Contributor Author

@elasticmachine merge upstream

@kibana-ci
Copy link
Collaborator

💚 Build Succeeded

Metrics [docs]

Public APIs missing comments

Total count of every public API that lacks a comment. Target amount is 0. Run node scripts/build_api_docs --plugin [yourplugin] --stats comments for more detailed information.

id before after diff
@kbn/core-saved-objects-migration-server-internal 81 85 +4
Unknown metric groups

API count

id before after diff
@kbn/core-saved-objects-migration-server-internal 116 120 +4

ESLint disabled in files

id before after diff
@kbn/core-saved-objects-migration-server-internal 0 2 +2

ESLint disabled line counts

id before after diff
securitySolution 432 435 +3

Total ESLint disabled count

id before after diff
@kbn/core-saved-objects-migration-server-internal 0 2 +2
securitySolution 512 515 +3
total +5

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

@pgayvallet pgayvallet merged commit 54df909 into elastic:main Apr 3, 2023
@kibanamachine kibanamachine added v8.8.0 backport:skip This commit does not require backporting labels Apr 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport:skip This commit does not require backporting Feature:Migrations release_note:skip Skip the PR/issue when compiling release notes Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc v8.8.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

DocumentMigrator: support downward migrations
6 participants