-
Notifications
You must be signed in to change notification settings - Fork 25.1k
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
Implement framework for migrating system indices #78951
Merged
Merged
Changes from all commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
03fdac2
Implement framework for migrating system indices
gwbrown 1371cd2
Remove extra `onResponse`
gwbrown d528882
Address Todos/cleanup
gwbrown 5f8b65e
Return the correct list when starting migration
gwbrown dabad50
Tweak docs
gwbrown 5cdff06
Merge branch 'master' into si/upgrade-framework
gwbrown 4c4d4b7
Merge branch 'master' into si/upgrade-framework
gwbrown acdcd34
Fix compilation after merge
gwbrown 4fe3373
Merge branch 'master' into si/upgrade-framework
gwbrown 1961c71
Fix test failures that expected fake responses
gwbrown 83415e4
Extract constant for upgrade version per review
gwbrown 932c0d0
Javadoc + cleanup per review
gwbrown 08f22f1
Rename results classes to clarify their relationship
gwbrown 7afd137
Merge branch 'master' into si/upgrade-framework
gwbrown 95356d9
Tests + always run on master node
gwbrown 4b2bbbf
Fix index name & doc count type
gwbrown fae67c0
Javadoc
gwbrown 4552e75
Plumb in callback metadata + test it
gwbrown 9cec136
Merge branch 'master' into si/upgrade-framework
gwbrown a84e781
Line length
gwbrown bad48b2
Verify that the pre/post migration hooks are called
gwbrown 998ca25
Set the origin before calling reindex
gwbrown a4fae04
Check results in upgrade callback + fix bug where results were update…
gwbrown 69986f2
Add multi-feature integration test
gwbrown ce074cf
Assert both feature names
gwbrown cd11bce
Cleanup
gwbrown 6c9c9da
Merge branch 'master' into si/upgrade-framework
gwbrown 1ac86ad
Fix unit tests for new logic
gwbrown c753724
Javadoc + missed renames per review
gwbrown cf61671
Copy aliases + alias assertions
gwbrown 9bddfbb
Merge branch 'master' into si/upgrade-framework
gwbrown 76e8aed
Add necessary system privileges
gwbrown 53da87f
Merge branch 'master' into si/upgrade-framework
gwbrown 427389e
Logging tweaks
gwbrown 59b70a0
Fix test for real version math
gwbrown 124c6d8
Adjust test for new system permissions
gwbrown 34fb47d
Merge branch 'master' into si/upgrade-framework
gwbrown File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
388 changes: 388 additions & 0 deletions
388
.../reindex/src/internalClusterTest/java/org/elasticsearch/migration/FeatureMigrationIT.java
Large diffs are not rendered by default.
Oops, something went wrong.
302 changes: 302 additions & 0 deletions
302
...dex/src/internalClusterTest/java/org/elasticsearch/migration/MultiFeatureMigrationIT.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,302 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
package org.elasticsearch.migration; | ||
|
||
import org.apache.lucene.util.SetOnce; | ||
import org.elasticsearch.Version; | ||
import org.elasticsearch.action.ActionListener; | ||
import org.elasticsearch.action.admin.cluster.migration.GetFeatureUpgradeStatusAction; | ||
import org.elasticsearch.action.admin.cluster.migration.GetFeatureUpgradeStatusRequest; | ||
import org.elasticsearch.action.admin.cluster.migration.GetFeatureUpgradeStatusResponse; | ||
import org.elasticsearch.action.admin.cluster.migration.PostFeatureUpgradeAction; | ||
import org.elasticsearch.action.admin.cluster.migration.PostFeatureUpgradeRequest; | ||
import org.elasticsearch.action.admin.cluster.migration.PostFeatureUpgradeResponse; | ||
import org.elasticsearch.client.Client; | ||
import org.elasticsearch.cluster.ClusterState; | ||
import org.elasticsearch.cluster.metadata.Metadata; | ||
import org.elasticsearch.cluster.service.ClusterService; | ||
import org.elasticsearch.common.Strings; | ||
import org.elasticsearch.common.settings.Settings; | ||
import org.elasticsearch.indices.SystemIndexDescriptor; | ||
import org.elasticsearch.plugins.Plugin; | ||
import org.elasticsearch.plugins.SystemIndexPlugin; | ||
import org.elasticsearch.reindex.ReindexPlugin; | ||
import org.elasticsearch.upgrades.FeatureMigrationResults; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.Collection; | ||
import java.util.Collections; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.concurrent.atomic.AtomicReference; | ||
import java.util.function.BiConsumer; | ||
import java.util.function.Function; | ||
import java.util.stream.Collectors; | ||
|
||
import static org.hamcrest.Matchers.aMapWithSize; | ||
import static org.hamcrest.Matchers.allOf; | ||
import static org.hamcrest.Matchers.equalTo; | ||
import static org.hamcrest.Matchers.hasEntry; | ||
import static org.hamcrest.Matchers.hasItems; | ||
import static org.hamcrest.Matchers.hasKey; | ||
import static org.hamcrest.Matchers.is; | ||
import static org.hamcrest.Matchers.notNullValue; | ||
import static org.hamcrest.Matchers.nullValue; | ||
|
||
public class MultiFeatureMigrationIT extends FeatureMigrationIT { | ||
|
||
@Override | ||
protected Settings nodeSettings(int nodeOrdinal, Settings otherSettings) { | ||
return Settings.builder().put(super.nodeSettings(nodeOrdinal, otherSettings)).build(); | ||
} | ||
|
||
@Override | ||
protected boolean forbidPrivateIndexSettings() { | ||
// We need to be able to set the index creation version manually. | ||
return false; | ||
} | ||
|
||
@Override | ||
protected Collection<Class<? extends Plugin>> nodePlugins() { | ||
List<Class<? extends Plugin>> plugins = new ArrayList<>(super.nodePlugins()); | ||
plugins.add(FeatureMigrationIT.TestPlugin.class); | ||
plugins.add(SecondPlugin.class); | ||
plugins.add(ReindexPlugin.class); | ||
return plugins; | ||
} | ||
|
||
// Sorts alphabetically after the feature from MultiFeatureMigrationIT | ||
private static final String SECOND_FEATURE_NAME = "B-test-feature"; | ||
private static final String ORIGIN = MultiFeatureMigrationIT.class.getSimpleName(); | ||
private static final String VERSION_META_KEY = "version"; | ||
static final int SECOND_FEATURE_IDX_FLAG_VALUE = 0; | ||
|
||
public void testMultipleFeatureMigration() throws Exception { | ||
// All the indices from FeatureMigrationIT | ||
createSystemIndexForDescriptor(INTERNAL_MANAGED); | ||
createSystemIndexForDescriptor(INTERNAL_UNMANAGED); | ||
createSystemIndexForDescriptor(EXTERNAL_MANAGED); | ||
createSystemIndexForDescriptor(EXTERNAL_UNMANAGED); | ||
// And our new one | ||
createSystemIndexForDescriptor(SECOND_FEATURE_IDX_DESCIPTOR); | ||
|
||
ensureGreen(); | ||
|
||
SetOnce<Boolean> preMigrationHookCalled = new SetOnce<>(); | ||
SetOnce<Boolean> postMigrationHookCalled = new SetOnce<>(); | ||
SetOnce<Boolean> secondPluginPreMigrationHookCalled = new SetOnce<>(); | ||
SetOnce<Boolean> secondPluginPostMigrationHookCalled = new SetOnce<>(); | ||
|
||
TestPlugin.preMigrationHook.set(clusterState -> { | ||
// None of the other hooks should have been called yet. | ||
assertThat(postMigrationHookCalled.get(), nullValue()); | ||
assertThat(secondPluginPreMigrationHookCalled.get(), nullValue()); | ||
assertThat(secondPluginPostMigrationHookCalled.get(), nullValue()); | ||
Map<String, Object> metadata = new HashMap<>(); | ||
metadata.put("stringKey", "first plugin value"); | ||
|
||
// We shouldn't have any results in the cluster state given no features have finished yet. | ||
FeatureMigrationResults currentResults = clusterState.metadata().custom(FeatureMigrationResults.TYPE); | ||
assertThat(currentResults, nullValue()); | ||
|
||
preMigrationHookCalled.set(true); | ||
return metadata; | ||
}); | ||
|
||
TestPlugin.postMigrationHook.set((clusterState, metadata) -> { | ||
// Check that the hooks have been called or not as expected. | ||
assertThat(preMigrationHookCalled.get(), is(true)); | ||
assertThat(secondPluginPreMigrationHookCalled.get(), nullValue()); | ||
assertThat(secondPluginPostMigrationHookCalled.get(), nullValue()); | ||
|
||
assertThat( | ||
metadata, | ||
hasEntry("stringKey", "first plugin value") | ||
); | ||
|
||
// We shouldn't have any results in the cluster state given no features have finished yet. | ||
FeatureMigrationResults currentResults = clusterState.metadata().custom(FeatureMigrationResults.TYPE); | ||
assertThat(currentResults, nullValue()); | ||
|
||
postMigrationHookCalled.set(true); | ||
}); | ||
|
||
SecondPlugin.preMigrationHook.set(clusterState -> { | ||
// Check that the hooks have been called or not as expected. | ||
assertThat(preMigrationHookCalled.get(), is(true)); | ||
assertThat(postMigrationHookCalled.get(), is(true)); | ||
assertThat(secondPluginPostMigrationHookCalled.get(), nullValue()); | ||
|
||
Map<String, Object> metadata = new HashMap<>(); | ||
metadata.put("stringKey", "second plugin value"); | ||
|
||
// But now, we should have results, as we're in a new feature! | ||
FeatureMigrationResults currentResults = clusterState.metadata().custom(FeatureMigrationResults.TYPE); | ||
assertThat(currentResults, notNullValue()); | ||
assertThat(currentResults.getFeatureStatuses(), allOf(aMapWithSize(1), hasKey(FEATURE_NAME))); | ||
assertThat(currentResults.getFeatureStatuses().get(FEATURE_NAME).succeeded(), is(true)); | ||
assertThat(currentResults.getFeatureStatuses().get(FEATURE_NAME).getFailedIndexName(), nullValue()); | ||
assertThat(currentResults.getFeatureStatuses().get(FEATURE_NAME).getException(), nullValue()); | ||
|
||
secondPluginPreMigrationHookCalled.set(true); | ||
return metadata; | ||
}); | ||
|
||
SecondPlugin.postMigrationHook.set((clusterState, metadata) -> { | ||
// Check that the hooks have been called or not as expected. | ||
assertThat(preMigrationHookCalled.get(), is(true)); | ||
assertThat(postMigrationHookCalled.get(), is(true)); | ||
assertThat(secondPluginPreMigrationHookCalled.get(), is(true)); | ||
|
||
assertThat( | ||
metadata, | ||
hasEntry("stringKey", "second plugin value") | ||
); | ||
|
||
// And here, the results should be the same, as we haven't updated the state with this feature's status yet. | ||
FeatureMigrationResults currentResults = clusterState.metadata().custom(FeatureMigrationResults.TYPE); | ||
assertThat(currentResults, notNullValue()); | ||
assertThat(currentResults.getFeatureStatuses(), allOf(aMapWithSize(1), hasKey(FEATURE_NAME))); | ||
assertThat(currentResults.getFeatureStatuses().get(FEATURE_NAME).succeeded(), is(true)); | ||
assertThat(currentResults.getFeatureStatuses().get(FEATURE_NAME).getFailedIndexName(), nullValue()); | ||
assertThat(currentResults.getFeatureStatuses().get(FEATURE_NAME).getException(), nullValue()); | ||
|
||
secondPluginPostMigrationHookCalled.set(true); | ||
}); | ||
|
||
PostFeatureUpgradeRequest migrationRequest = new PostFeatureUpgradeRequest(); | ||
PostFeatureUpgradeResponse migrationResponse = client().execute(PostFeatureUpgradeAction.INSTANCE, migrationRequest).get(); | ||
assertThat(migrationResponse.getReason(), nullValue()); | ||
assertThat(migrationResponse.getElasticsearchException(), nullValue()); | ||
final Set<String> migratingFeatures = migrationResponse.getFeatures() | ||
.stream() | ||
.map(PostFeatureUpgradeResponse.Feature::getFeatureName) | ||
.collect(Collectors.toSet()); | ||
assertThat(migratingFeatures, hasItems(FEATURE_NAME, SECOND_FEATURE_NAME)); | ||
|
||
GetFeatureUpgradeStatusRequest getStatusRequest = new GetFeatureUpgradeStatusRequest(); | ||
assertBusy(() -> { | ||
GetFeatureUpgradeStatusResponse statusResponse = client().execute(GetFeatureUpgradeStatusAction.INSTANCE, getStatusRequest) | ||
.get(); | ||
logger.info(Strings.toString(statusResponse)); | ||
assertThat(statusResponse.getUpgradeStatus(), equalTo(GetFeatureUpgradeStatusResponse.UpgradeStatus.NO_MIGRATION_NEEDED)); | ||
}); | ||
|
||
assertTrue("the first plugin's pre-migration hook wasn't actually called", preMigrationHookCalled.get()); | ||
assertTrue("the first plugin's post-migration hook wasn't actually called", postMigrationHookCalled.get()); | ||
|
||
assertTrue("the second plugin's pre-migration hook wasn't actually called", secondPluginPreMigrationHookCalled.get()); | ||
assertTrue("the second plugin's post-migration hook wasn't actually called", secondPluginPostMigrationHookCalled.get()); | ||
|
||
Metadata finalMetadata = client().admin().cluster().prepareState().get().getState().metadata(); | ||
// Check that the results metadata is what we expect | ||
FeatureMigrationResults currentResults = finalMetadata.custom(FeatureMigrationResults.TYPE); | ||
assertThat(currentResults, notNullValue()); | ||
assertThat(currentResults.getFeatureStatuses(), allOf(aMapWithSize(2), hasKey(FEATURE_NAME), hasKey(SECOND_FEATURE_NAME))); | ||
assertThat(currentResults.getFeatureStatuses().get(FEATURE_NAME).succeeded(), is(true)); | ||
assertThat(currentResults.getFeatureStatuses().get(FEATURE_NAME).getFailedIndexName(), nullValue()); | ||
assertThat(currentResults.getFeatureStatuses().get(FEATURE_NAME).getException(), nullValue()); | ||
assertThat(currentResults.getFeatureStatuses().get(SECOND_FEATURE_NAME).succeeded(), is(true)); | ||
assertThat(currentResults.getFeatureStatuses().get(SECOND_FEATURE_NAME).getFailedIndexName(), nullValue()); | ||
assertThat(currentResults.getFeatureStatuses().get(SECOND_FEATURE_NAME).getException(), nullValue()); | ||
|
||
// Finally, verify that all the indices exist and have the properties we expect. | ||
assertIndexHasCorrectProperties( | ||
finalMetadata, | ||
".int-man-old-reindexed-for-8", | ||
INTERNAL_MANAGED_FLAG_VALUE, | ||
true, | ||
true, | ||
Arrays.asList(".int-man-old", ".internal-managed-alias") | ||
); | ||
assertIndexHasCorrectProperties( | ||
finalMetadata, | ||
".int-unman-old-reindexed-for-8", | ||
INTERNAL_UNMANAGED_FLAG_VALUE, | ||
false, | ||
true, | ||
Collections.singletonList(".int-unman-old") | ||
); | ||
assertIndexHasCorrectProperties( | ||
finalMetadata, | ||
".ext-man-old-reindexed-for-8", | ||
EXTERNAL_MANAGED_FLAG_VALUE, | ||
true, | ||
false, | ||
Arrays.asList(".ext-man-old", ".external-managed-alias") | ||
); | ||
assertIndexHasCorrectProperties( | ||
finalMetadata, | ||
".ext-unman-old-reindexed-for-8", | ||
EXTERNAL_UNMANAGED_FLAG_VALUE, | ||
false, | ||
false, | ||
Collections.singletonList(".ext-unman-old") | ||
); | ||
|
||
assertIndexHasCorrectProperties( | ||
finalMetadata, | ||
".second-int-man-old-reindexed-for-8", | ||
SECOND_FEATURE_IDX_FLAG_VALUE, | ||
true, | ||
true, | ||
Arrays.asList(".second-int-man-old", ".second-internal-managed-alias") | ||
); | ||
} | ||
|
||
private static final SystemIndexDescriptor SECOND_FEATURE_IDX_DESCIPTOR = SystemIndexDescriptor.builder() | ||
.setIndexPattern(".second-int-man-*") | ||
.setAliasName(".second-internal-managed-alias") | ||
.setPrimaryIndex(".second-int-man-old") | ||
.setType(SystemIndexDescriptor.Type.INTERNAL_MANAGED) | ||
.setSettings(createSimpleSettings(Version.V_7_0_0, 0)) | ||
.setMappings(createSimpleMapping(true, true)) | ||
.setOrigin(ORIGIN) | ||
.setVersionMetaKey(VERSION_META_KEY) | ||
.setAllowedElasticProductOrigins(Collections.emptyList()) | ||
.setMinimumNodeVersion(Version.V_7_0_0) | ||
.setPriorSystemIndexDescriptors(Collections.emptyList()) | ||
.build(); | ||
|
||
public static class SecondPlugin extends Plugin implements SystemIndexPlugin { | ||
|
||
private static final AtomicReference<Function<ClusterState, Map<String, Object>>> preMigrationHook = new AtomicReference<>(); | ||
private static final AtomicReference<BiConsumer<ClusterState, Map<String, Object>>> postMigrationHook = new AtomicReference<>(); | ||
|
||
public SecondPlugin() { | ||
|
||
} | ||
|
||
@Override public String getFeatureName() { | ||
return SECOND_FEATURE_NAME; | ||
} | ||
|
||
@Override public String getFeatureDescription() { | ||
return "a plugin for test system index migration with multiple features"; | ||
} | ||
|
||
@Override public Collection<SystemIndexDescriptor> getSystemIndexDescriptors(Settings settings) { | ||
return Collections.singletonList(SECOND_FEATURE_IDX_DESCIPTOR); | ||
} | ||
|
||
@Override public void prepareForIndicesMigration( | ||
ClusterService clusterService, Client client, ActionListener<Map<String, Object>> listener) { | ||
listener.onResponse(preMigrationHook.get().apply(clusterService.state())); | ||
} | ||
|
||
@Override public void indicesMigrationComplete( | ||
Map<String, Object> preUpgradeMetadata, ClusterService clusterService, Client client, ActionListener<Boolean> listener) { | ||
postMigrationHook.get().accept(clusterService.state(), preUpgradeMetadata); | ||
listener.onResponse(true); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like this way of verifying the order in which these hooks are called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I can't take credit for it, I picked this style up from uh... somewhere else in Elasticsearch, not sure where exactly.