-
Notifications
You must be signed in to change notification settings - Fork 25.6k
Allow passing several reserved state chunks in single process call #124574
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
Changes from 1 commit
4095a58
f72fe17
698cc49
307cbc0
3351ab1
0393b87
9817ffb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,7 @@ | |
| import org.elasticsearch.cluster.routing.RerouteService; | ||
| import org.elasticsearch.cluster.service.ClusterService; | ||
| import org.elasticsearch.common.Priority; | ||
| import org.elasticsearch.common.util.set.Sets; | ||
| import org.elasticsearch.core.FixForMultiProject; | ||
| import org.elasticsearch.core.Tuple; | ||
| import org.elasticsearch.env.BuildVersion; | ||
|
|
@@ -187,7 +188,7 @@ public void process( | |
| process(namespace, stateChunk, versionCheck, errorListener); | ||
| } | ||
|
|
||
| ReservedStateChunk parse(ProjectId projectId, String namespace, XContentParser parser) { | ||
| public ReservedStateChunk parse(ProjectId projectId, String namespace, XContentParser parser) { | ||
| try { | ||
| return stateChunkParser.apply(parser, null); | ||
| } catch (Exception e) { | ||
|
|
@@ -377,6 +378,7 @@ public void onFailure(Exception e) { | |
| * @param projectId the project state to modify | ||
| * @param namespace the namespace under which we'll store the reserved keys in the cluster state metadata | ||
| * @param reservedStateChunk a {@link ReservedStateChunk} composite state object to process | ||
| * @param versionCheck Enum representing whether a reserved state should be processed based on the current and new versions | ||
| * @param errorListener a consumer called with {@link IllegalStateException} if the content has errors and the | ||
| * cluster state cannot be correctly applied, null if successful or the state failed to apply because of incompatible version. | ||
| */ | ||
|
|
@@ -387,17 +389,43 @@ public void process( | |
| ReservedStateVersionCheck versionCheck, | ||
| Consumer<Exception> errorListener | ||
| ) { | ||
| Map<String, Object> reservedState = reservedStateChunk.state(); | ||
| ReservedStateVersion reservedStateVersion = reservedStateChunk.metadata(); | ||
| process(projectId, namespace, List.of(reservedStateChunk), versionCheck, errorListener); | ||
| } | ||
|
|
||
| /** | ||
| * Saves and reserves a chunk of the cluster state under a given 'namespace' from {@link XContentParser} by combining several chunks | ||
| * into one | ||
| * | ||
| * @param projectId the project state to modify | ||
| * @param namespace the namespace under which we'll store the reserved keys in the cluster state metadata | ||
| * @param reservedStateChunks a list of {@link ReservedStateChunk} composite state objects to process | ||
| * @param versionCheck Enum representing whether a reserved state should be processed based on the current and new versions | ||
| * @param errorListener a consumer called with {@link IllegalStateException} if the content has errors and the | ||
| * cluster state cannot be correctly applied, null if successful or the state failed to apply because of incompatible version. | ||
| */ | ||
| public void process( | ||
| ProjectId projectId, | ||
| String namespace, | ||
| List<ReservedStateChunk> reservedStateChunks, | ||
| ReservedStateVersionCheck versionCheck, | ||
| Consumer<Exception> errorListener | ||
| ) { | ||
| ReservedStateChunk reservedStateChunk; | ||
| ReservedStateVersion reservedStateVersion; | ||
| LinkedHashSet<String> orderedHandlers; | ||
|
|
||
| try { | ||
| reservedStateChunk = reservedStateChunks.size() == 1 | ||
| ? reservedStateChunks.getFirst() | ||
| : mergeReservedStateChunks(reservedStateChunks); | ||
|
||
| Map<String, Object> reservedState = reservedStateChunk.state(); | ||
| reservedStateVersion = reservedStateChunk.metadata(); | ||
| orderedHandlers = orderedProjectStateHandlers(reservedState.keySet()); | ||
| } catch (Exception e) { | ||
| ErrorState errorState = new ErrorState( | ||
| projectId, | ||
| namespace, | ||
| reservedStateVersion.version(), | ||
| reservedStateChunks.getFirst().metadata().version(), | ||
| versionCheck, | ||
| e, | ||
| ReservedStateErrorMetadata.ErrorKind.PARSING | ||
|
|
@@ -476,6 +504,32 @@ public void onFailure(Exception e) { | |
| ); | ||
| } | ||
|
|
||
| private static ReservedStateChunk mergeReservedStateChunks(List<ReservedStateChunk> chunks) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we are merging the list of chunks into a single There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we do want to change the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for pointing this out! I've spent some time thinking about this.
Before this change With this change we call
The purpose of this change is to be able to spread the state for a single namespace across several chunks. Since reserved state is versioned per namespace I think it makes the most sense to have a single one for both. The alternative I think you're proposing is to have several reserved state namespaces in a single cluster state update. We'd then have to add version dependencies between namespaces, which I think is out of scope for the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Thanks for explaining. I see the issue now.
After commenting on the other PR about passing down both project settings and secrets at the same time, I now think it might be better to use a single namespace as you have proposed. Sorry for the back and forth. |
||
| if (chunks.isEmpty()) { | ||
| throw new IllegalArgumentException("No chunks provided"); | ||
| } | ||
|
|
||
| ReservedStateVersion reservedStateVersion = chunks.getFirst().metadata(); | ||
| Map<String, Object> mergedChunks = new HashMap<>(chunks.size()); | ||
| for (var chunk : chunks) { | ||
| Set<String> duplicateKeys = Sets.intersection(chunk.state().keySet(), mergedChunks.keySet()); | ||
| if (chunk.metadata().equals(reservedStateVersion) == false) { | ||
| throw new IllegalStateException( | ||
| "Failed to merge reserved state chunks because of version mismatch: [" | ||
| + reservedStateVersion | ||
| + "] != [" | ||
| + chunk.metadata() | ||
| + "]" | ||
| ); | ||
| } else if (duplicateKeys.isEmpty() == false) { | ||
| throw new IllegalStateException("Failed to merge reserved state chunks because of duplicate keys: " + duplicateKeys); | ||
| } | ||
| mergedChunks.putAll(chunk.state()); | ||
| } | ||
|
|
||
| return new ReservedStateChunk(mergedChunks, reservedStateVersion); | ||
| } | ||
|
|
||
| // package private for testing | ||
| Exception checkAndReportError( | ||
| Optional<ProjectId> projectId, | ||
|
|
@@ -518,6 +572,11 @@ void updateErrorState(ErrorState errorState) { | |
| return; | ||
| } | ||
|
|
||
| if (errorState.projectId().isPresent() && clusterService.state().metadata().hasProject(errorState.projectId().get()) == false) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was a bug. If parsing fails for a project that doesn't exist yet, the error state reporting won't work since it's persisted in the project metadata that doesn't exist yet. This case needs to be handled in the caller (multi-project file service). |
||
| // Can't update error state for a project that doesn't exist yet | ||
| return; | ||
| } | ||
|
|
||
| submitErrorUpdateTask(errorState); | ||
| } | ||
|
|
||
|
|
||
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.
This can be used by a caller to parse several state chunks before passing them to process.