From 56c67a266ee53356bda6bc1422f3ba097276cadc Mon Sep 17 00:00:00 2001 From: Sean Zatz Date: Wed, 13 Aug 2025 12:50:08 -0400 Subject: [PATCH 1/8] Add index mode to resolve index response. --- .../25_resolve_index_with_mode.yml | 74 +++++++++++++++++++ .../org/elasticsearch/TransportVersions.java | 1 + .../indices/resolve/ResolveIndexAction.java | 33 ++++++++- .../indices/resolve/ResolveIndexTests.java | 37 +++++++--- 4 files changed, 130 insertions(+), 15 deletions(-) create mode 100644 rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.resolve_index/25_resolve_index_with_mode.yml diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.resolve_index/25_resolve_index_with_mode.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.resolve_index/25_resolve_index_with_mode.yml new file mode 100644 index 0000000000000..e82c052675608 --- /dev/null +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.resolve_index/25_resolve_index_with_mode.yml @@ -0,0 +1,74 @@ +--- +setup: + - do: + indices.delete: + index: my-std-index + ignore_unavailable: true + - do: + indices.delete: + index: my-ts-index + ignore_unavailable: true + +# Only run this test if the cluster supports time series indexing. +# If your project uses a different feature flag name, adjust it here. +--- +"resolve index returns mode for standard and time_series indices": + - skip: + cluster_features: ["tsdb_indexing"] + reason: "Requires time series indexing support" + + # Create a standard index + - do: + indices.create: + index: my-std-index + body: + settings: + number_of_shards: 1 + number_of_replicas: 0 + + # Create a time-series index + - do: + indices.create: + index: my-ts-index + body: + settings: + index.mode: time_series + number_of_shards: 1 + number_of_replicas: 0 + index.routing_path: ["host"] + mappings: + properties: + "@timestamp": + type: date + host: + type: keyword + time_series_dimension: true + metric: + type: keyword + value: + type: double + + # Resolve standard index and verify mode + - do: + indices.resolve_index: + name: my-std-index + - match: { indices.0.name: "my-std-index" } + - match: { indices.0.mode: "standard" } + + # Resolve time-series index and verify mode + - do: + indices.resolve_index: + name: my-ts-index + - match: { indices.0.name: "my-ts-index" } + - match: { indices.0.mode: "time_series" } + +--- +teardown: + - do: + indices.delete: + index: my-std-index + ignore_unavailable: true + - do: + indices.delete: + index: my-ts-index + ignore_unavailable: true diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index 3fff7513053d1..627898a6e1283 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -355,6 +355,7 @@ static TransportVersion def(int id) { public static final TransportVersion TO_CHILD_BLOCK_JOIN_QUERY = def(9_133_0_00); public static final TransportVersion ML_INFERENCE_AI21_COMPLETION_ADDED = def(9_134_0_00); public static final TransportVersion TRANSPORT_NODE_USAGE_STATS_FOR_THREAD_POOLS_ACTION = def(9_135_0_00); + public static final TransportVersion RESOLVE_INDEX_MODE_ADDED = def(9_136_0_00); /* * STOP! READ THIS FIRST! No, really, diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexAction.java b/server/src/main/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexAction.java index 67e19f0d4459a..132ca5aaa439b 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexAction.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexAction.java @@ -9,6 +9,7 @@ package org.elasticsearch.action.admin.indices.resolve; +import org.elasticsearch.TransportVersions; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.ActionResponse; @@ -39,6 +40,7 @@ import org.elasticsearch.common.util.concurrent.EsExecutors; import org.elasticsearch.core.Nullable; import org.elasticsearch.index.Index; +import org.elasticsearch.index.IndexMode; import org.elasticsearch.injection.guice.Inject; import org.elasticsearch.search.SearchService; import org.elasticsearch.tasks.Task; @@ -176,27 +178,39 @@ public static class ResolvedIndex extends ResolvedIndexAbstraction implements Wr static final ParseField ALIASES_FIELD = new ParseField("aliases"); static final ParseField ATTRIBUTES_FIELD = new ParseField("attributes"); static final ParseField DATA_STREAM_FIELD = new ParseField("data_stream"); + static final ParseField MODE_FIELD = new ParseField("mode"); private final String[] aliases; private final String[] attributes; private final String dataStream; + private final String mode; ResolvedIndex(StreamInput in) throws IOException { setName(in.readString()); this.aliases = in.readStringArray(); this.attributes = in.readStringArray(); this.dataStream = in.readOptionalString(); + if (in.getTransportVersion().onOrAfter(TransportVersions.RESOLVE_INDEX_MODE_ADDED)) { + this.mode = in.readOptionalString(); + } else { + this.mode = null; + } } ResolvedIndex(String name, String[] aliases, String[] attributes, @Nullable String dataStream) { + this(name, aliases, attributes, dataStream, null); + } + + ResolvedIndex(String name, String[] aliases, String[] attributes, @Nullable String dataStream, String mode) { super(name); this.aliases = aliases; this.attributes = attributes; this.dataStream = dataStream; + this.mode = mode; } public ResolvedIndex copy(String newName) { - return new ResolvedIndex(newName, aliases, attributes, dataStream); + return new ResolvedIndex(newName, aliases, attributes, dataStream, mode); } public String[] getAliases() { @@ -211,12 +225,19 @@ public String getDataStream() { return dataStream; } + public String getMode() { + return mode; + } + @Override public void writeTo(StreamOutput out) throws IOException { out.writeString(getName()); out.writeStringArray(aliases); out.writeStringArray(attributes); out.writeOptionalString(dataStream); + if (out.getTransportVersion().onOrAfter(TransportVersions.RESOLVE_INDEX_MODE_ADDED)) { + out.writeOptionalString(mode); + } } @Override @@ -230,6 +251,9 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws if (Strings.isNullOrEmpty(dataStream) == false) { builder.field(DATA_STREAM_FIELD.getPreferredName(), dataStream); } + if (Strings.isNullOrEmpty(mode) == false) { + builder.field(MODE_FIELD.getPreferredName(), mode); + } builder.endObject(); return builder; } @@ -242,12 +266,14 @@ public boolean equals(Object o) { return getName().equals(index.getName()) && Objects.equals(dataStream, index.dataStream) && Arrays.equals(aliases, index.aliases) - && Arrays.equals(attributes, index.attributes); + && Arrays.equals(attributes, index.attributes) + && Objects.equals(mode, index.mode); } @Override public int hashCode() { int result = Objects.hash(getName(), dataStream); + result = 31 * result + Objects.hashCode(mode); result = 31 * result + Arrays.hashCode(aliases); result = 31 * result + Arrays.hashCode(attributes); return result; @@ -639,7 +665,8 @@ private static void enrichIndexAbstraction( ia.getName(), aliasNames, attributes.stream().map(Enum::name).map(e -> e.toLowerCase(Locale.ROOT)).toArray(String[]::new), - ia.getParentDataStream() == null ? null : ia.getParentDataStream().getName() + ia.getParentDataStream() == null ? null : ia.getParentDataStream().getName(), + writeIndex.getIndexMode() == null ? IndexMode.STANDARD.toString() : writeIndex.getIndexMode().getName() ) ); } diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexTests.java index 576d6c2d03b35..8f48c4c52b659 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexTests.java @@ -32,6 +32,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.time.DateFormatter; import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.index.IndexMode; import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.index.IndexVersion; import org.elasticsearch.indices.SystemIndexDescriptor; @@ -68,14 +69,22 @@ public class ResolveIndexTests extends ESTestCase { private final Object[][] indices = new Object[][] { - // name, isClosed, isHidden, isSystem, isFrozen, dataStream, aliases - { "logs-pgsql-prod-20200101", false, false, false, true, null, new String[] { "logs-pgsql-prod" } }, - { "logs-pgsql-prod-20200102", false, false, false, true, null, new String[] { "logs-pgsql-prod", "one-off-alias" } }, - { "logs-pgsql-prod-20200103", false, false, false, false, null, new String[] { "logs-pgsql-prod" } }, - { "logs-pgsql-test-20200101", true, false, false, false, null, new String[] { "logs-pgsql-test" } }, - { "logs-pgsql-test-20200102", false, false, false, false, null, new String[] { "logs-pgsql-test" } }, - { "logs-pgsql-test-20200103", false, false, false, false, null, new String[] { "logs-pgsql-test" } }, - { ".test-system-index", false, false, true, false, null, new String[] {} } }; + // name, isClosed, isHidden, isSystem, isFrozen, dataStream, aliases, mode + { "logs-pgsql-prod-20200101", false, false, false, true, null, new String[] { "logs-pgsql-prod" }, IndexMode.STANDARD }, + { + "logs-pgsql-prod-20200102", + false, + false, + false, + true, + null, + new String[] { "logs-pgsql-prod", "one-off-alias" }, + IndexMode.TIME_SERIES }, + { "logs-pgsql-prod-20200103", false, false, false, false, null, new String[] { "logs-pgsql-prod" }, IndexMode.STANDARD }, + { "logs-pgsql-test-20200101", true, false, false, false, null, new String[] { "logs-pgsql-test" }, IndexMode.STANDARD }, + { "logs-pgsql-test-20200102", false, false, false, false, null, new String[] { "logs-pgsql-test" }, IndexMode.STANDARD }, + { "logs-pgsql-test-20200103", false, false, false, false, null, new String[] { "logs-pgsql-test" }, IndexMode.STANDARD }, + { ".test-system-index", false, false, true, false, null, new String[] {}, IndexMode.STANDARD } }; private final Object[][] dataStreams = new Object[][] { // name, numBackingIndices @@ -356,6 +365,7 @@ private void validateIndices(List resolvedIndices, String... expe assertThat(resolvedIndex.getAliases(), is(((String[]) indexInfo[6]))); assertThat(resolvedIndex.getAttributes(), is(flagsToAttributes(indexInfo))); assertThat(resolvedIndex.getDataStream(), equalTo((String) indexInfo[5])); + assertThat(resolvedIndex.getMode(), equalTo((IndexMode) indexInfo[7])); } } @@ -444,7 +454,8 @@ private ProjectMetadata.Builder buildProjectMetadata(ProjectId projectId, Object boolean hidden = (boolean) indexInfo[2]; boolean system = (boolean) indexInfo[3]; boolean frozen = (boolean) indexInfo[4]; - allIndices.add(createIndexMetadata(indexName, aliases, closed, hidden, system, frozen)); + IndexMode mode = (IndexMode) indexInfo[7]; + allIndices.add(createIndexMetadata(indexName, aliases, closed, hidden, system, frozen, mode)); } for (IndexMetadata index : allIndices) { @@ -460,12 +471,14 @@ private static IndexMetadata createIndexMetadata( boolean closed, boolean hidden, boolean system, - boolean frozen + boolean frozen, + IndexMode mode ) { Settings.Builder settingsBuilder = Settings.builder() .put(IndexMetadata.SETTING_VERSION_CREATED, IndexVersion.current()) .put("index.hidden", hidden) - .put("index.frozen", frozen); + .put("index.frozen", frozen) + .put("index.mode", mode.toString()); IndexMetadata.Builder indexBuilder = IndexMetadata.builder(name) .settings(settingsBuilder) @@ -482,7 +495,7 @@ private static IndexMetadata createIndexMetadata( } private static IndexMetadata createIndexMetadata(String name, boolean hidden) { - return createIndexMetadata(name, Strings.EMPTY_ARRAY, false, true, false, false); + return createIndexMetadata(name, Strings.EMPTY_ARRAY, false, true, false, false, IndexMode.STANDARD); } private static Object[] findInfo(Object[][] indexSource, String indexName) { From 47a34f8d3ca0f2175f82052a2c1302aa764e0fed Mon Sep 17 00:00:00 2001 From: Sean Zatz Date: Wed, 13 Aug 2025 17:28:11 -0400 Subject: [PATCH 2/8] Update yaml rest test requirements --- .../indices.resolve_index/25_resolve_index_with_mode.yml | 6 +++--- .../action/admin/indices/resolve/ResolveIndexTests.java | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.resolve_index/25_resolve_index_with_mode.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.resolve_index/25_resolve_index_with_mode.yml index e82c052675608..e83a2313ae05c 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.resolve_index/25_resolve_index_with_mode.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.resolve_index/25_resolve_index_with_mode.yml @@ -13,9 +13,9 @@ setup: # If your project uses a different feature flag name, adjust it here. --- "resolve index returns mode for standard and time_series indices": - - skip: - cluster_features: ["tsdb_indexing"] - reason: "Requires time series indexing support" + - requires: + cluster_features: ["gte_v8.5.0"] + reason: "Requires time series indexing support introduced in v8.5.0" # Create a standard index - do: diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexTests.java index 8f48c4c52b659..3941e0331b9e3 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexTests.java @@ -365,7 +365,7 @@ private void validateIndices(List resolvedIndices, String... expe assertThat(resolvedIndex.getAliases(), is(((String[]) indexInfo[6]))); assertThat(resolvedIndex.getAttributes(), is(flagsToAttributes(indexInfo))); assertThat(resolvedIndex.getDataStream(), equalTo((String) indexInfo[5])); - assertThat(resolvedIndex.getMode(), equalTo((IndexMode) indexInfo[7])); + assertThat(resolvedIndex.getMode(), equalTo(((IndexMode) indexInfo[7]).toString())); } } @@ -520,7 +520,8 @@ private Object[] findBackingIndexInfo(Object[][] dataStreamSource, String indexN false, false, dataStreamName, - Strings.EMPTY_ARRAY }; + Strings.EMPTY_ARRAY, + IndexMode.STANDARD }; } } } From 9dc729468234b6cbbd2aabfa8c2f4630b07d9b96 Mon Sep 17 00:00:00 2001 From: Sean Zatz Date: Wed, 13 Aug 2025 18:25:31 -0400 Subject: [PATCH 3/8] Fix unit test --- .../action/admin/indices/resolve/ResolveIndexTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexTests.java index 3941e0331b9e3..b0f62b29b6697 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexTests.java @@ -243,8 +243,8 @@ public void testResolveHiddenProperlyWithDateMath() { String tomorrowSuffix = dateFormatter.format(now.plus(Duration.ofDays(1L))); Object[][] indices = new Object[][] { // name, isClosed, isHidden, isFrozen, dataStream, aliases - { "logs-pgsql-prod-" + todaySuffix, false, true, false, false, null, Strings.EMPTY_ARRAY }, - { "logs-pgsql-prod-" + tomorrowSuffix, false, true, false, false, null, Strings.EMPTY_ARRAY } }; + { "logs-pgsql-prod-" + todaySuffix, false, true, false, false, null, Strings.EMPTY_ARRAY, IndexMode.STANDARD }, + { "logs-pgsql-prod-" + tomorrowSuffix, false, true, false, false, null, Strings.EMPTY_ARRAY, IndexMode.STANDARD } }; final ProjectMetadata project = buildProjectMetadata(randomProjectIdOrDefault(), new Object[][] {}, indices).build(); String[] requestedIndex = new String[] { "" }; Set resolvedIndices = resolver.resolveExpressions( From bd62d5936898120ec402df6cc2291d892f37928d Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Wed, 13 Aug 2025 22:52:08 +0000 Subject: [PATCH 4/8] [CI] Auto commit changes from spotless --- server/src/main/java/org/elasticsearch/TransportVersions.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index 160dc25418b33..991095126b58c 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -366,7 +366,6 @@ static TransportVersion def(int id) { public static final TransportVersion ESQL_LOOKUP_JOIN_ON_MANY_FIELDS = def(9_139_0_00); public static final TransportVersion RESOLVE_INDEX_MODE_ADDED = def(9_140_0_00); - /* * STOP! READ THIS FIRST! No, really, * ____ _____ ___ ____ _ ____ _____ _ ____ _____ _ _ ___ ____ _____ ___ ____ ____ _____ _ From 7d7510c0d5a578f1af523dcdf234e4905e796681 Mon Sep 17 00:00:00 2001 From: Sean Zatz Date: Thu, 14 Aug 2025 09:26:03 -0400 Subject: [PATCH 5/8] Update docs/changelog/132858.yaml --- docs/changelog/132858.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 docs/changelog/132858.yaml diff --git a/docs/changelog/132858.yaml b/docs/changelog/132858.yaml new file mode 100644 index 0000000000000..73d08deb3a5b6 --- /dev/null +++ b/docs/changelog/132858.yaml @@ -0,0 +1,5 @@ +pr: 132858 +summary: Add index mode to resolve index response +area: Indices APIs +type: feature +issues: [] From 4c6afcff286d420bb1ccc490d77d328c58b55ba6 Mon Sep 17 00:00:00 2001 From: Sean Zatz Date: Fri, 15 Aug 2025 16:18:09 -0400 Subject: [PATCH 6/8] BWC changes and more test cases added --- .../25_resolve_index_with_mode.yml | 4 +- server/src/main/java/module-info.java | 3 +- .../indices/resolve/ResolveIndexAction.java | 20 +++--- .../resolve/ResolveIndexResponseTests.java | 9 ++- .../indices/resolve/ResolveIndexTests.java | 2 +- .../ResolvedIndexSerializingTests.java | 66 +++++++++++++++++++ .../TransportResolveClusterActionTests.java | 16 ++--- 7 files changed, 94 insertions(+), 26 deletions(-) create mode 100644 server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolvedIndexSerializingTests.java diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.resolve_index/25_resolve_index_with_mode.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.resolve_index/25_resolve_index_with_mode.yml index e83a2313ae05c..8627597d79268 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.resolve_index/25_resolve_index_with_mode.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.resolve_index/25_resolve_index_with_mode.yml @@ -14,8 +14,8 @@ setup: --- "resolve index returns mode for standard and time_series indices": - requires: - cluster_features: ["gte_v8.5.0"] - reason: "Requires time series indexing support introduced in v8.5.0" + cluster_features: ["gte_v8.5.0", "resolve_index_returns_mode"] + reason: "Requires time series indexing support introduced in v8.5.0 & Node must support returning 'mode' in indices.resolve_index response" # Create a standard index - do: diff --git a/server/src/main/java/module-info.java b/server/src/main/java/module-info.java index c3d6d2f4d97df..549c603b13980 100644 --- a/server/src/main/java/module-info.java +++ b/server/src/main/java/module-info.java @@ -434,7 +434,8 @@ org.elasticsearch.script.ScriptFeatures, org.elasticsearch.search.retriever.RetrieversFeatures, org.elasticsearch.action.admin.cluster.stats.ClusterStatsFeatures, - org.elasticsearch.ingest.IngestFeatures; + org.elasticsearch.ingest.IngestFeatures, + org.elasticsearch.action.admin.indices.resolve.ResolveIndexFeatures; uses org.elasticsearch.plugins.internal.SettingsExtension; uses RestExtension; diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexAction.java b/server/src/main/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexAction.java index 132ca5aaa439b..a105a570864f4 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexAction.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexAction.java @@ -183,7 +183,7 @@ public static class ResolvedIndex extends ResolvedIndexAbstraction implements Wr private final String[] aliases; private final String[] attributes; private final String dataStream; - private final String mode; + private final IndexMode mode; ResolvedIndex(StreamInput in) throws IOException { setName(in.readString()); @@ -191,17 +191,13 @@ public static class ResolvedIndex extends ResolvedIndexAbstraction implements Wr this.attributes = in.readStringArray(); this.dataStream = in.readOptionalString(); if (in.getTransportVersion().onOrAfter(TransportVersions.RESOLVE_INDEX_MODE_ADDED)) { - this.mode = in.readOptionalString(); + this.mode = IndexMode.readFrom(in); } else { this.mode = null; } } - ResolvedIndex(String name, String[] aliases, String[] attributes, @Nullable String dataStream) { - this(name, aliases, attributes, dataStream, null); - } - - ResolvedIndex(String name, String[] aliases, String[] attributes, @Nullable String dataStream, String mode) { + ResolvedIndex(String name, String[] aliases, String[] attributes, @Nullable String dataStream, IndexMode mode) { super(name); this.aliases = aliases; this.attributes = attributes; @@ -225,7 +221,7 @@ public String getDataStream() { return dataStream; } - public String getMode() { + public IndexMode getMode() { return mode; } @@ -236,7 +232,7 @@ public void writeTo(StreamOutput out) throws IOException { out.writeStringArray(attributes); out.writeOptionalString(dataStream); if (out.getTransportVersion().onOrAfter(TransportVersions.RESOLVE_INDEX_MODE_ADDED)) { - out.writeOptionalString(mode); + IndexMode.writeTo(mode, out); } } @@ -251,8 +247,8 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws if (Strings.isNullOrEmpty(dataStream) == false) { builder.field(DATA_STREAM_FIELD.getPreferredName(), dataStream); } - if (Strings.isNullOrEmpty(mode) == false) { - builder.field(MODE_FIELD.getPreferredName(), mode); + if (mode != null) { + builder.field(MODE_FIELD.getPreferredName(), mode.toString()); } builder.endObject(); return builder; @@ -666,7 +662,7 @@ private static void enrichIndexAbstraction( aliasNames, attributes.stream().map(Enum::name).map(e -> e.toLowerCase(Locale.ROOT)).toArray(String[]::new), ia.getParentDataStream() == null ? null : ia.getParentDataStream().getName(), - writeIndex.getIndexMode() == null ? IndexMode.STANDARD.toString() : writeIndex.getIndexMode().getName() + writeIndex.getIndexMode() == null ? IndexMode.STANDARD : writeIndex.getIndexMode() ) ); } diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexResponseTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexResponseTests.java index 5707101b5e5ef..184a5b8b18783 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexResponseTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexResponseTests.java @@ -15,6 +15,7 @@ import org.elasticsearch.action.admin.indices.resolve.ResolveIndexAction.Response; import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.index.IndexMode; import org.elasticsearch.test.AbstractXContentSerializingTestCase; import org.elasticsearch.xcontent.ConstructingObjectParser; import org.elasticsearch.xcontent.XContentParser; @@ -28,6 +29,7 @@ import static org.elasticsearch.action.admin.indices.resolve.ResolveIndexAction.ResolvedIndex.ALIASES_FIELD; import static org.elasticsearch.action.admin.indices.resolve.ResolveIndexAction.ResolvedIndex.ATTRIBUTES_FIELD; import static org.elasticsearch.action.admin.indices.resolve.ResolveIndexAction.ResolvedIndex.DATA_STREAM_FIELD; +import static org.elasticsearch.action.admin.indices.resolve.ResolveIndexAction.ResolvedIndex.MODE_FIELD; import static org.elasticsearch.action.admin.indices.resolve.ResolveIndexAction.ResolvedIndexAbstraction.NAME_FIELD; import static org.elasticsearch.action.admin.indices.resolve.ResolveIndexAction.Response.DATA_STREAMS_FIELD; import static org.elasticsearch.action.admin.indices.resolve.ResolveIndexAction.Response.INDICES_FIELD; @@ -76,8 +78,9 @@ private static ResolvedIndex createTestResolvedIndexInstance() { String[] aliases = randomStringArray(0, 5); String[] attributes = randomSubsetOf(List.of("open", "hidden", "frozen")).toArray(Strings.EMPTY_ARRAY); String dataStream = randomBoolean() ? randomAlphaOfLength(6) : null; + IndexMode mode = randomFrom(IndexMode.values()); - return new ResolvedIndex(name, aliases, attributes, dataStream); + return new ResolvedIndex(name, aliases, attributes, dataStream, mode); } private static ResolvedAlias createTestResolvedAliasInstance() { @@ -109,7 +112,8 @@ static String[] randomStringArray(int minLength, int maxLength) { (String) args[0], args[1] != null ? ((List) args[1]).toArray(Strings.EMPTY_ARRAY) : new String[0], ((List) args[2]).toArray(Strings.EMPTY_ARRAY), - (String) args[3] + (String) args[3], + IndexMode.fromString((String) args[4]) ) ); @SuppressWarnings("unchecked") @@ -133,6 +137,7 @@ static String[] randomStringArray(int minLength, int maxLength) { INDEX_PARSER.declareStringArray(ConstructingObjectParser.optionalConstructorArg(), ALIASES_FIELD); INDEX_PARSER.declareStringArray(ConstructingObjectParser.constructorArg(), ATTRIBUTES_FIELD); INDEX_PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), DATA_STREAM_FIELD); + INDEX_PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), MODE_FIELD); ALIAS_PARSER.declareString(ConstructingObjectParser.constructorArg(), NAME_FIELD); ALIAS_PARSER.declareStringArray(ConstructingObjectParser.constructorArg(), INDICES_FIELD); RESPONSE_PARSER.declareObjectArray(ConstructingObjectParser.constructorArg(), (p, c) -> indexFromXContent(p), INDICES_FIELD); diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexTests.java index b0f62b29b6697..8dbc50aae0eb7 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexTests.java @@ -365,7 +365,7 @@ private void validateIndices(List resolvedIndices, String... expe assertThat(resolvedIndex.getAliases(), is(((String[]) indexInfo[6]))); assertThat(resolvedIndex.getAttributes(), is(flagsToAttributes(indexInfo))); assertThat(resolvedIndex.getDataStream(), equalTo((String) indexInfo[5])); - assertThat(resolvedIndex.getMode(), equalTo(((IndexMode) indexInfo[7]).toString())); + assertThat(resolvedIndex.getMode().toString(), equalTo(((IndexMode) indexInfo[7]).toString())); } } diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolvedIndexSerializingTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolvedIndexSerializingTests.java new file mode 100644 index 0000000000000..079a92b7a5222 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/ResolvedIndexSerializingTests.java @@ -0,0 +1,66 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.action.admin.indices.resolve; + +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.index.IndexMode; +import org.elasticsearch.test.AbstractWireSerializingTestCase; + +import java.util.ArrayList; +import java.util.List; + +public class ResolvedIndexSerializingTests extends AbstractWireSerializingTestCase { + + @Override + protected Writeable.Reader instanceReader() { + return ResolveIndexAction.ResolvedIndex::new; + } + + @Override + protected ResolveIndexAction.ResolvedIndex mutateInstance(ResolveIndexAction.ResolvedIndex instance) { + String name = instance.getName(); + String[] aliases = instance.getAliases(); + String[] attributes = instance.getAttributes(); + String dataStream = instance.getDataStream(); + IndexMode mode = instance.getMode(); + mode = randomValueOtherThan(mode, () -> randomFrom(IndexMode.values())); + return new ResolveIndexAction.ResolvedIndex(name, aliases, attributes, dataStream, mode); + } + + @Override + protected ResolveIndexAction.ResolvedIndex createTestInstance() { + return createTestItem(); + } + + private static ResolveIndexAction.ResolvedIndex createTestItem() { + // Random index name + final String name = randomAlphaOfLengthBetween(5, 20); + + // Random aliases (possibly empty) + final String[] aliases = randomBoolean() + ? new String[0] + : randomArray(0, 4, String[]::new, () -> randomAlphaOfLengthBetween(3, 15)); + + // Attributes: always one of "open"/"closed", plus optional flags + final List attrs = new ArrayList<>(); + attrs.add(randomBoolean() ? "open" : "closed"); + if (randomBoolean()) attrs.add("hidden"); + if (randomBoolean()) attrs.add("system"); + if (randomBoolean()) attrs.add("frozen"); + final String[] attributes = attrs.toArray(new String[0]); + + final String dataStream = randomBoolean() ? randomAlphaOfLengthBetween(3, 15) : null; + + final IndexMode mode = randomFrom(IndexMode.values()); + + return new ResolveIndexAction.ResolvedIndex(name, aliases, attributes, dataStream, mode); + + } +} diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/TransportResolveClusterActionTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/TransportResolveClusterActionTests.java index 824ad22b1af20..da1e940131d6d 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/TransportResolveClusterActionTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/resolve/TransportResolveClusterActionTests.java @@ -104,30 +104,30 @@ public void testHasNonClosedMatchingIndex() { // as long as there is one non-closed index it should return true indices = new ArrayList<>(); - indices.add(new ResolveIndexAction.ResolvedIndex("foo", null, new String[] { "open" }, ".ds-foo")); + indices.add(new ResolveIndexAction.ResolvedIndex("foo", null, new String[] { "open" }, ".ds-foo", null)); assertThat(TransportResolveClusterAction.hasNonClosedMatchingIndex(indices), equalTo(true)); - indices.add(new ResolveIndexAction.ResolvedIndex("bar", null, new String[] { "system" }, ".ds-bar")); + indices.add(new ResolveIndexAction.ResolvedIndex("bar", null, new String[] { "system" }, ".ds-bar", null)); assertThat(TransportResolveClusterAction.hasNonClosedMatchingIndex(indices), equalTo(true)); - indices.add(new ResolveIndexAction.ResolvedIndex("baz", null, new String[] { "system", "open", "hidden" }, null)); + indices.add(new ResolveIndexAction.ResolvedIndex("baz", null, new String[] { "system", "open", "hidden" }, null, null)); assertThat(TransportResolveClusterAction.hasNonClosedMatchingIndex(indices), equalTo(true)); - indices.add(new ResolveIndexAction.ResolvedIndex("quux", null, new String[0], null)); + indices.add(new ResolveIndexAction.ResolvedIndex("quux", null, new String[0], null, null)); assertThat(TransportResolveClusterAction.hasNonClosedMatchingIndex(indices), equalTo(true)); - indices.add(new ResolveIndexAction.ResolvedIndex("wibble", null, new String[] { "system", "closed" }, null)); + indices.add(new ResolveIndexAction.ResolvedIndex("wibble", null, new String[] { "system", "closed" }, null, null)); assertThat(TransportResolveClusterAction.hasNonClosedMatchingIndex(indices), equalTo(true)); // if only closed indexes are present, should return false indices.clear(); - indices.add(new ResolveIndexAction.ResolvedIndex("wibble", null, new String[] { "system", "closed" }, null)); + indices.add(new ResolveIndexAction.ResolvedIndex("wibble", null, new String[] { "system", "closed" }, null, null)); assertThat(TransportResolveClusterAction.hasNonClosedMatchingIndex(indices), equalTo(false)); - indices.add(new ResolveIndexAction.ResolvedIndex("wobble", null, new String[] { "closed" }, null)); + indices.add(new ResolveIndexAction.ResolvedIndex("wobble", null, new String[] { "closed" }, null, null)); assertThat(TransportResolveClusterAction.hasNonClosedMatchingIndex(indices), equalTo(false)); // now add a non-closed index and should return true - indices.add(new ResolveIndexAction.ResolvedIndex("aaa", null, new String[] { "hidden" }, null)); + indices.add(new ResolveIndexAction.ResolvedIndex("aaa", null, new String[] { "hidden" }, null, null)); assertThat(TransportResolveClusterAction.hasNonClosedMatchingIndex(indices), equalTo(true)); } } From e599a1a0ab13ff93609d216da6f54655dfaef4e7 Mon Sep 17 00:00:00 2001 From: Sean Zatz Date: Fri, 15 Aug 2025 16:18:54 -0400 Subject: [PATCH 7/8] BWC changes and more test cases added --- .../indices/resolve/ResolveIndexFeatures.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 server/src/main/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexFeatures.java diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexFeatures.java b/server/src/main/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexFeatures.java new file mode 100644 index 0000000000000..994f128484b66 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/resolve/ResolveIndexFeatures.java @@ -0,0 +1,32 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.action.admin.indices.resolve; + +import org.elasticsearch.features.FeatureSpecification; +import org.elasticsearch.features.NodeFeature; + +import java.util.Set; + +public class ResolveIndexFeatures implements FeatureSpecification { + + // Feature published by nodes that return "mode" in indices.resolve_index responses. + public static final NodeFeature RESOLVE_INDEX_RETURNS_MODE = new NodeFeature("resolve_index_returns_mode"); + + @Override + public Set getFeatures() { + return Set.of(RESOLVE_INDEX_RETURNS_MODE); + } + + @Override + public Set getTestFeatures() { + return Set.of(RESOLVE_INDEX_RETURNS_MODE); + } + +} From fbdf04ee7cc5f3ca3493638d10d70651d191152e Mon Sep 17 00:00:00 2001 From: Sean Zatz Date: Fri, 15 Aug 2025 16:22:58 -0400 Subject: [PATCH 8/8] BWC changes and more test cases added --- .../services/org.elasticsearch.features.FeatureSpecification | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/main/resources/META-INF/services/org.elasticsearch.features.FeatureSpecification b/server/src/main/resources/META-INF/services/org.elasticsearch.features.FeatureSpecification index 677a5a96891b5..42bf3c942daaf 100644 --- a/server/src/main/resources/META-INF/services/org.elasticsearch.features.FeatureSpecification +++ b/server/src/main/resources/META-INF/services/org.elasticsearch.features.FeatureSpecification @@ -20,3 +20,4 @@ org.elasticsearch.script.ScriptFeatures org.elasticsearch.cluster.routing.RoutingFeatures org.elasticsearch.action.admin.cluster.stats.ClusterStatsFeatures org.elasticsearch.ingest.IngestFeatures +org.elasticsearch.action.admin.indices.resolve.ResolveIndexFeatures