Skip to content

Commit 0c2cf3f

Browse files
committed
Merge branch 'main' of https://github.com/elastic/elasticsearch into esql-reranker-boostrap
2 parents 69d551f + c58ac45 commit 0c2cf3f

File tree

42 files changed

+3547
-376
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+3547
-376
lines changed

docs/changelog/124574.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 124574
2+
summary: Allow passing several reserved state chunks in single process call
3+
area: Infra/Settings
4+
type: enhancement
5+
issues: []

muted-tests.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -390,9 +390,6 @@ tests:
390390
- class: org.elasticsearch.backwards.MixedClusterClientYamlTestSuiteIT
391391
method: test {p0=search/610_function_score/Random}
392392
issue: https://github.com/elastic/elasticsearch/issues/125010
393-
- class: org.elasticsearch.packaging.test.DockerTests
394-
method: test010Install
395-
issue: https://github.com/elastic/elasticsearch/issues/119441
396393
- class: org.elasticsearch.xpack.ilm.DataStreamAndIndexLifecycleMixingTests
397394
method: testGetDataStreamResponse
398395
issue: https://github.com/elastic/elasticsearch/issues/125083

plugins/examples/security-authorization-engine/src/main/java/org/elasticsearch/example/CustomAuthorizationEngine.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
package org.elasticsearch.example;
1111

1212
import org.elasticsearch.action.ActionListener;
13+
import org.elasticsearch.action.support.IndexComponentSelector;
1314
import org.elasticsearch.action.support.SubscribableListener;
1415
import org.elasticsearch.cluster.metadata.IndexAbstraction;
1516
import org.elasticsearch.cluster.metadata.ProjectMetadata;
@@ -35,7 +36,6 @@
3536
import java.util.Arrays;
3637
import java.util.Collection;
3738
import java.util.Collections;
38-
import java.util.function.Supplier;
3939
import java.util.HashMap;
4040
import java.util.LinkedHashMap;
4141
import java.util.List;
@@ -119,19 +119,19 @@ public void loadAuthorizedIndices(
119119
) {
120120
if (isSuperuser(requestInfo.getAuthentication().getEffectiveSubject().getUser())) {
121121
listener.onResponse(new AuthorizedIndices() {
122-
public Supplier<Set<String>> all() {
122+
public Set<String> all(IndexComponentSelector selector) {
123123
return () -> indicesLookup.keySet();
124124
}
125-
public boolean check(String name) {
125+
public boolean check(String name, IndexComponentSelector selector) {
126126
return indicesLookup.containsKey(name);
127127
}
128128
});
129129
} else {
130130
listener.onResponse(new AuthorizedIndices() {
131-
public Supplier<Set<String>> all() {
131+
public Set<String> all(IndexComponentSelector selector) {
132132
return () -> Set.of();
133133
}
134-
public boolean check(String name) {
134+
public boolean check(String name, IndexComponentSelector selector) {
135135
return false;
136136
}
137137
});

qa/packaging/src/test/java/org/elasticsearch/packaging/util/docker/Docker.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ public static void waitForElasticsearchToStart() {
187187
Thread.sleep(STARTUP_SLEEP_INTERVAL_MILLISECONDS);
188188

189189
// Set COLUMNS so that `ps` doesn't truncate its output
190-
psOutput = dockerShell.run("bash -c 'COLUMNS=3000 ps ax'").stdout();
190+
psOutput = dockerShell.run("bash -c 'COLUMNS=4000 ps ax'").stdout();
191191

192192
if (psOutput.contains("org.elasticsearch.bootstrap.Elasticsearch")) {
193193
isElasticsearchRunning = true;

server/src/main/java/org/elasticsearch/TransportVersions.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ static TransportVersion def(int id) {
149149
public static final TransportVersion INFERENCE_CONTEXT_8_X = def(8_841_0_08);
150150
public static final TransportVersion ML_INFERENCE_DEEPSEEK_8_19 = def(8_841_0_09);
151151
public static final TransportVersion ESQL_SERIALIZE_BLOCK_TYPE_CODE_8_19 = def(8_841_0_10);
152+
public static final TransportVersion ESQL_FAILURE_FROM_REMOTE_8_19 = def(8_841_0_11);
152153
public static final TransportVersion INITIAL_ELASTICSEARCH_9_0 = def(9_000_0_00);
153154
public static final TransportVersion REMOVE_SNAPSHOT_FAILURES_90 = def(9_000_0_01);
154155
public static final TransportVersion TRANSPORT_STATS_HANDLING_TIME_REQUIRED_90 = def(9_000_0_02);

server/src/main/java/org/elasticsearch/action/support/IndexComponentSelector.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,24 @@ public static IndexComponentSelector getByKey(String key) {
7272
return KEY_REGISTRY.get(key);
7373
}
7474

75+
/**
76+
* Like {@link #getByKey(String)} but throws an exception if the key is not recognised.
77+
* @return the selector if recognized. `null` input will return `DATA`.
78+
* @throws IllegalArgumentException if the key was not recognised.
79+
*/
80+
public static IndexComponentSelector getByKeyOrThrow(@Nullable String key) {
81+
if (key == null) {
82+
return DATA;
83+
}
84+
IndexComponentSelector selector = getByKey(key);
85+
if (selector == null) {
86+
throw new IllegalArgumentException(
87+
"Unknown key of index component selector [" + key + "], available options are: " + KEY_REGISTRY.keySet()
88+
);
89+
}
90+
return selector;
91+
}
92+
7593
public static IndexComponentSelector read(StreamInput in) throws IOException {
7694
byte id = in.readByte();
7795
if (in.getTransportVersion().onOrAfter(TransportVersions.REMOVE_ALL_APPLICABLE_SELECTOR)

server/src/main/java/org/elasticsearch/cluster/metadata/IndexAbstraction.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,13 @@ default boolean isDataStreamRelated() {
100100
return false;
101101
}
102102

103+
/**
104+
* @return whether this index abstraction is a failure index of a data stream
105+
*/
106+
default boolean isFailureIndexOfDataStream() {
107+
return false;
108+
}
109+
103110
/**
104111
* An index abstraction type.
105112
*/
@@ -183,6 +190,11 @@ public DataStream getParentDataStream() {
183190
return dataStream;
184191
}
185192

193+
@Override
194+
public boolean isFailureIndexOfDataStream() {
195+
return getParentDataStream() != null && getParentDataStream().isFailureStoreIndex(getName());
196+
}
197+
186198
@Override
187199
public boolean isHidden() {
188200
return isHidden;

server/src/main/java/org/elasticsearch/cluster/metadata/IndexAbstractionResolver.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
import java.util.HashSet;
2323
import java.util.List;
2424
import java.util.Set;
25-
import java.util.function.Predicate;
26-
import java.util.function.Supplier;
25+
import java.util.function.BiPredicate;
26+
import java.util.function.Function;
2727

2828
public class IndexAbstractionResolver {
2929

@@ -37,8 +37,8 @@ public List<String> resolveIndexAbstractions(
3737
Iterable<String> indices,
3838
IndicesOptions indicesOptions,
3939
ProjectMetadata projectMetadata,
40-
Supplier<Set<String>> allAuthorizedAndAvailable,
41-
Predicate<String> isAuthorized,
40+
Function<IndexComponentSelector, Set<String>> allAuthorizedAndAvailableBySelector,
41+
BiPredicate<String, IndexComponentSelector> isAuthorized,
4242
boolean includeDataStreams
4343
) {
4444
List<String> finalIndices = new ArrayList<>();
@@ -64,14 +64,15 @@ public List<String> resolveIndexAbstractions(
6464
);
6565
}
6666
indexAbstraction = expressionAndSelector.v1();
67+
IndexComponentSelector selector = IndexComponentSelector.getByKeyOrThrow(selectorString);
6768

6869
// we always need to check for date math expressions
6970
indexAbstraction = IndexNameExpressionResolver.resolveDateMathExpression(indexAbstraction);
7071

7172
if (indicesOptions.expandWildcardExpressions() && Regex.isSimpleMatchPattern(indexAbstraction)) {
7273
wildcardSeen = true;
7374
Set<String> resolvedIndices = new HashSet<>();
74-
for (String authorizedIndex : allAuthorizedAndAvailable.get()) {
75+
for (String authorizedIndex : allAuthorizedAndAvailableBySelector.apply(selector)) {
7576
if (Regex.simpleMatch(indexAbstraction, authorizedIndex)
7677
&& isIndexVisible(
7778
indexAbstraction,
@@ -102,7 +103,7 @@ && isIndexVisible(
102103
resolveSelectorsAndCollect(indexAbstraction, selectorString, indicesOptions, resolvedIndices, projectMetadata);
103104
if (minus) {
104105
finalIndices.removeAll(resolvedIndices);
105-
} else if (indicesOptions.ignoreUnavailable() == false || isAuthorized.test(indexAbstraction)) {
106+
} else if (indicesOptions.ignoreUnavailable() == false || isAuthorized.test(indexAbstraction, selector)) {
106107
// Unauthorized names are considered unavailable, so if `ignoreUnavailable` is `true` they should be silently
107108
// discarded from the `finalIndices` list. Other "ways of unavailable" must be handled by the action
108109
// handler, see: https://github.com/elastic/elasticsearch/issues/90215

server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.elasticsearch.common.util.CollectionUtils;
2929
import org.elasticsearch.common.util.concurrent.ThreadContext;
3030
import org.elasticsearch.common.util.set.Sets;
31+
import org.elasticsearch.core.Assertions;
3132
import org.elasticsearch.core.Nullable;
3233
import org.elasticsearch.core.Predicates;
3334
import org.elasticsearch.core.Tuple;
@@ -1001,6 +1002,14 @@ public static boolean hasSelectorSuffix(String expression) {
10011002
return expression.contains(SelectorResolver.SELECTOR_SEPARATOR);
10021003
}
10031004

1005+
public static boolean hasSelector(@Nullable String expression, IndexComponentSelector selector) {
1006+
Objects.requireNonNull(selector, "null selectors not supported");
1007+
if (expression == null) {
1008+
return false;
1009+
}
1010+
return expression.endsWith(SelectorResolver.SELECTOR_SEPARATOR + selector.getKey());
1011+
}
1012+
10041013
/**
10051014
* @return If the specified string is a selector expression then this method returns the base expression and its selector part.
10061015
*/
@@ -1022,6 +1031,14 @@ public static String combineSelectorExpression(String baseExpression, @Nullable
10221031
: (baseExpression + SelectorResolver.SELECTOR_SEPARATOR + selectorExpression);
10231032
}
10241033

1034+
public static void assertExpressionHasNullOrDataSelector(String expression) {
1035+
if (Assertions.ENABLED) {
1036+
var tuple = splitSelectorExpression(expression);
1037+
assert tuple.v2() == null || IndexComponentSelector.DATA.getKey().equals(tuple.v2())
1038+
: "Expected expression [" + expression + "] to have a data selector but found [" + tuple.v2() + "]";
1039+
}
1040+
}
1041+
10251042
/**
10261043
* Resolve an array of expressions to the set of indices and aliases that these expressions match.
10271044
*/

server/src/main/java/org/elasticsearch/reservedstate/service/ReservedClusterStateService.java

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.elasticsearch.cluster.routing.RerouteService;
2323
import org.elasticsearch.cluster.service.ClusterService;
2424
import org.elasticsearch.common.Priority;
25+
import org.elasticsearch.common.util.set.Sets;
2526
import org.elasticsearch.core.FixForMultiProject;
2627
import org.elasticsearch.core.Tuple;
2728
import org.elasticsearch.env.BuildVersion;
@@ -187,7 +188,7 @@ public void process(
187188
process(namespace, stateChunk, versionCheck, errorListener);
188189
}
189190

190-
ReservedStateChunk parse(ProjectId projectId, String namespace, XContentParser parser) {
191+
public ReservedStateChunk parse(ProjectId projectId, String namespace, XContentParser parser) {
191192
try {
192193
return stateChunkParser.apply(parser, null);
193194
} catch (Exception e) {
@@ -377,6 +378,7 @@ public void onFailure(Exception e) {
377378
* @param projectId the project state to modify
378379
* @param namespace the namespace under which we'll store the reserved keys in the cluster state metadata
379380
* @param reservedStateChunk a {@link ReservedStateChunk} composite state object to process
381+
* @param versionCheck Enum representing whether a reserved state should be processed based on the current and new versions
380382
* @param errorListener a consumer called with {@link IllegalStateException} if the content has errors and the
381383
* cluster state cannot be correctly applied, null if successful or the state failed to apply because of incompatible version.
382384
*/
@@ -387,17 +389,41 @@ public void process(
387389
ReservedStateVersionCheck versionCheck,
388390
Consumer<Exception> errorListener
389391
) {
390-
Map<String, Object> reservedState = reservedStateChunk.state();
391-
ReservedStateVersion reservedStateVersion = reservedStateChunk.metadata();
392+
process(projectId, namespace, List.of(reservedStateChunk), versionCheck, errorListener);
393+
}
392394

395+
/**
396+
* Saves and reserves a chunk of the cluster state under a given 'namespace' from {@link XContentParser} by combining several chunks
397+
* into one
398+
*
399+
* @param projectId the project state to modify
400+
* @param namespace the namespace under which we'll store the reserved keys in the cluster state metadata
401+
* @param reservedStateChunks a list of {@link ReservedStateChunk} composite state objects to process
402+
* @param versionCheck Enum representing whether a reserved state should be processed based on the current and new versions
403+
* @param errorListener a consumer called with {@link IllegalStateException} if the content has errors and the
404+
* cluster state cannot be correctly applied, null if successful or the state failed to apply because of incompatible version.
405+
*/
406+
public void process(
407+
ProjectId projectId,
408+
String namespace,
409+
List<ReservedStateChunk> reservedStateChunks,
410+
ReservedStateVersionCheck versionCheck,
411+
Consumer<Exception> errorListener
412+
) {
413+
ReservedStateChunk reservedStateChunk;
414+
ReservedStateVersion reservedStateVersion;
393415
LinkedHashSet<String> orderedHandlers;
416+
394417
try {
418+
reservedStateChunk = mergeReservedStateChunks(reservedStateChunks);
419+
Map<String, Object> reservedState = reservedStateChunk.state();
420+
reservedStateVersion = reservedStateChunk.metadata();
395421
orderedHandlers = orderedProjectStateHandlers(reservedState.keySet());
396422
} catch (Exception e) {
397423
ErrorState errorState = new ErrorState(
398424
projectId,
399425
namespace,
400-
reservedStateVersion.version(),
426+
reservedStateChunks.getFirst().metadata().version(),
401427
versionCheck,
402428
e,
403429
ReservedStateErrorMetadata.ErrorKind.PARSING
@@ -476,6 +502,36 @@ public void onFailure(Exception e) {
476502
);
477503
}
478504

505+
private static ReservedStateChunk mergeReservedStateChunks(List<ReservedStateChunk> chunks) {
506+
if (chunks.isEmpty()) {
507+
throw new IllegalArgumentException("No chunks provided");
508+
}
509+
510+
if (chunks.size() == 1) {
511+
return chunks.getFirst();
512+
}
513+
514+
ReservedStateVersion reservedStateVersion = chunks.getFirst().metadata();
515+
Map<String, Object> mergedChunks = new HashMap<>(chunks.size());
516+
for (var chunk : chunks) {
517+
Set<String> duplicateKeys = Sets.intersection(chunk.state().keySet(), mergedChunks.keySet());
518+
if (chunk.metadata().equals(reservedStateVersion) == false) {
519+
throw new IllegalStateException(
520+
"Failed to merge reserved state chunks because of version mismatch: ["
521+
+ reservedStateVersion
522+
+ "] != ["
523+
+ chunk.metadata()
524+
+ "]"
525+
);
526+
} else if (duplicateKeys.isEmpty() == false) {
527+
throw new IllegalStateException("Failed to merge reserved state chunks because of duplicate keys: " + duplicateKeys);
528+
}
529+
mergedChunks.putAll(chunk.state());
530+
}
531+
532+
return new ReservedStateChunk(mergedChunks, reservedStateVersion);
533+
}
534+
479535
// package private for testing
480536
Exception checkAndReportError(
481537
Optional<ProjectId> projectId,
@@ -518,6 +574,11 @@ void updateErrorState(ErrorState errorState) {
518574
return;
519575
}
520576

577+
if (errorState.projectId().isPresent() && clusterService.state().metadata().hasProject(errorState.projectId().get()) == false) {
578+
// Can't update error state for a project that doesn't exist yet
579+
return;
580+
}
581+
521582
submitErrorUpdateTask(errorState);
522583
}
523584

0 commit comments

Comments
 (0)