diff --git a/build.gradle b/build.gradle index a29c783422172..67128426eebd0 100644 --- a/build.gradle +++ b/build.gradle @@ -170,8 +170,8 @@ task verifyVersions { * the enabled state of every bwc task. It should be set back to true * after the backport of the backcompat code is complete. */ -final boolean bwc_tests_enabled = true -final String bwc_tests_disabled_issue = "" /* place a PR link here when commiting bwc changes */ +final boolean bwc_tests_enabled = false +final String bwc_tests_disabled_issue = "https://github.com/elastic/elasticsearch/pull/31675" /* place a PR link here when commiting bwc changes */ if (bwc_tests_enabled == false) { if (bwc_tests_disabled_issue.isEmpty()) { throw new GradleException("bwc_tests_disabled_issue must be set when bwc_tests_enabled == false") diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/GetAliasesResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/GetAliasesResponseTests.java index 5f3354ad2b95d..c5bc74e7517c3 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/GetAliasesResponseTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/GetAliasesResponseTests.java @@ -59,7 +59,7 @@ private static Map> createIndicesAliasesMap(int min, return map; } - private static AliasMetaData createAliasMetaData() { + public static AliasMetaData createAliasMetaData() { AliasMetaData.Builder builder = AliasMetaData.builder(randomAlphaOfLengthBetween(3, 10)); if (randomBoolean()) { builder.routing(randomAlphaOfLengthBetween(3, 10)); diff --git a/server/src/main/java/org/elasticsearch/action/ActionModule.java b/server/src/main/java/org/elasticsearch/action/ActionModule.java index 48e1cef08d00a..58efce77c9fd8 100644 --- a/server/src/main/java/org/elasticsearch/action/ActionModule.java +++ b/server/src/main/java/org/elasticsearch/action/ActionModule.java @@ -557,7 +557,7 @@ public void initRestHandlers(Supplier nodesInCluster) { registerHandler.accept(new RestRestoreSnapshotAction(settings, restController)); registerHandler.accept(new RestDeleteSnapshotAction(settings, restController)); registerHandler.accept(new RestSnapshotsStatusAction(settings, restController)); - registerHandler.accept(new RestGetIndicesAction(settings, restController, indexScopedSettings, settingsFilter)); + registerHandler.accept(new RestGetIndicesAction(settings, restController)); registerHandler.accept(new RestIndicesStatsAction(settings, restController)); registerHandler.accept(new RestIndicesSegmentsAction(settings, restController)); registerHandler.accept(new RestIndicesShardStoresAction(settings, restController)); diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/get/GetIndexRequest.java b/server/src/main/java/org/elasticsearch/action/admin/indices/get/GetIndexRequest.java index 02a7a8ad79fbe..fb8bd6b5684be 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/get/GetIndexRequest.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/get/GetIndexRequest.java @@ -19,6 +19,7 @@ package org.elasticsearch.action.admin.indices.get; +import org.elasticsearch.Version; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.support.master.info.ClusterInfoRequest; import org.elasticsearch.common.io.stream.StreamInput; @@ -80,6 +81,9 @@ public GetIndexRequest(StreamInput in) throws IOException { features[i] = Feature.fromId(in.readByte()); } humanReadable = in.readBoolean(); + if (in.getVersion().onOrAfter(Version.V_6_4_0)) { + includeDefaults = in.readBoolean(); + } } public GetIndexRequest features(Feature... features) { @@ -119,8 +123,7 @@ public boolean humanReadable() { /** * Sets the value of "include_defaults". - * Used only by the high-level REST client. - * + * * @param includeDefaults value of "include_defaults" to be set. * @return this request */ @@ -131,8 +134,7 @@ public GetIndexRequest includeDefaults(boolean includeDefaults) { /** * Whether to return all default settings for each of the indices. - * Used only by the high-level REST client. - * + * * @return true if defaults settings for each of the indices need to returned; * false otherwise. */ @@ -153,6 +155,9 @@ public void writeTo(StreamOutput out) throws IOException { out.writeByte(feature.id); } out.writeBoolean(humanReadable); + if (out.getVersion().onOrAfter(Version.V_6_4_0)) { + out.writeBoolean(includeDefaults); + } } } diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/get/GetIndexResponse.java b/server/src/main/java/org/elasticsearch/action/admin/indices/get/GetIndexResponse.java index 36bfa81a33416..e2b72077b7f21 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/get/GetIndexResponse.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/get/GetIndexResponse.java @@ -20,33 +20,50 @@ package org.elasticsearch.action.admin.indices.get; import com.carrotsearch.hppc.cursors.ObjectObjectCursor; +import org.apache.lucene.util.CollectionUtil; +import org.elasticsearch.Version; import org.elasticsearch.action.ActionResponse; import org.elasticsearch.cluster.metadata.AliasMetaData; import org.elasticsearch.cluster.metadata.MappingMetaData; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.collect.ImmutableOpenMap; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentParser.Token; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.List; +import java.util.Objects; + +import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; /** - * A response for a delete index action. + * A response for a get index action. */ -public class GetIndexResponse extends ActionResponse { +public class GetIndexResponse extends ActionResponse implements ToXContentObject { private ImmutableOpenMap> mappings = ImmutableOpenMap.of(); private ImmutableOpenMap> aliases = ImmutableOpenMap.of(); private ImmutableOpenMap settings = ImmutableOpenMap.of(); + private ImmutableOpenMap defaultSettings = ImmutableOpenMap.of(); private String[] indices; GetIndexResponse(String[] indices, - ImmutableOpenMap> mappings, - ImmutableOpenMap> aliases, ImmutableOpenMap settings) { + ImmutableOpenMap> mappings, + ImmutableOpenMap> aliases, + ImmutableOpenMap settings, + ImmutableOpenMap defaultSettings) { this.indices = indices; + // to have deterministic order + Arrays.sort(indices); if (mappings != null) { this.mappings = mappings; } @@ -56,6 +73,9 @@ public class GetIndexResponse extends ActionResponse { if (settings != null) { this.settings = settings; } + if (defaultSettings != null) { + this.defaultSettings = defaultSettings; + } } GetIndexResponse() { @@ -89,14 +109,51 @@ public ImmutableOpenMap settings() { return settings; } + /** + * If the originating {@link GetIndexRequest} object was configured to include + * defaults, this will contain a mapping of index name to {@link Settings} objects. + * The returned {@link Settings} objects will contain only those settings taking + * effect as defaults. Any settings explicitly set on the index will be available + * via {@link #settings()}. + * See also {@link GetIndexRequest#includeDefaults(boolean)} + */ + public ImmutableOpenMap defaultSettings() { + return defaultSettings; + } + public ImmutableOpenMap getSettings() { return settings(); } + /** + * Returns the string value for the specified index and setting. If the includeDefaults flag was not set or set to + * false on the {@link GetIndexRequest}, this method will only return a value where the setting was explicitly set + * on the index. If the includeDefaults flag was set to true on the {@link GetIndexRequest}, this method will fall + * back to return the default value if the setting was not explicitly set. + */ + public String getSetting(String index, String setting) { + Settings indexSettings = settings.get(index); + if (setting != null) { + if (indexSettings != null && indexSettings.hasValue(setting)) { + return indexSettings.get(setting); + } else { + Settings defaultIndexSettings = defaultSettings.get(index); + if (defaultIndexSettings != null) { + return defaultIndexSettings.get(setting); + } else { + return null; + } + } + } else { + return null; + } + } + @Override public void readFrom(StreamInput in) throws IOException { super.readFrom(in); this.indices = in.readStringArray(); + int mappingsSize = in.readVInt(); ImmutableOpenMap.Builder> mappingsMapBuilder = ImmutableOpenMap.builder(); for (int i = 0; i < mappingsSize; i++) { @@ -109,6 +166,7 @@ public void readFrom(StreamInput in) throws IOException { mappingsMapBuilder.put(key, mappingEntryBuilder.build()); } mappings = mappingsMapBuilder.build(); + int aliasesSize = in.readVInt(); ImmutableOpenMap.Builder> aliasesMapBuilder = ImmutableOpenMap.builder(); for (int i = 0; i < aliasesSize; i++) { @@ -121,6 +179,7 @@ public void readFrom(StreamInput in) throws IOException { aliasesMapBuilder.put(key, Collections.unmodifiableList(aliasEntryBuilder)); } aliases = aliasesMapBuilder.build(); + int settingsSize = in.readVInt(); ImmutableOpenMap.Builder settingsMapBuilder = ImmutableOpenMap.builder(); for (int i = 0; i < settingsSize; i++) { @@ -128,6 +187,15 @@ public void readFrom(StreamInput in) throws IOException { settingsMapBuilder.put(key, Settings.readSettingsFromStream(in)); } settings = settingsMapBuilder.build(); + + ImmutableOpenMap.Builder defaultSettingsMapBuilder = ImmutableOpenMap.builder(); + if (in.getVersion().onOrAfter(Version.V_6_4_0)) { + int defaultSettingsSize = in.readVInt(); + for (int i = 0; i < defaultSettingsSize ; i++) { + defaultSettingsMapBuilder.put(in.readString(), Settings.readSettingsFromStream(in)); + } + } + defaultSettings = defaultSettingsMapBuilder.build(); } @Override @@ -156,5 +224,202 @@ public void writeTo(StreamOutput out) throws IOException { out.writeString(indexEntry.key); Settings.writeSettingsToStream(indexEntry.value, out); } + if (out.getVersion().onOrAfter(Version.V_6_4_0)) { + out.writeVInt(defaultSettings.size()); + for (ObjectObjectCursor indexEntry : defaultSettings) { + out.writeString(indexEntry.key); + Settings.writeSettingsToStream(indexEntry.value, out); + } + } + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + { + for (final String index : indices) { + builder.startObject(index); + { + builder.startObject("aliases"); + List indexAliases = aliases.get(index); + if (indexAliases != null) { + for (final AliasMetaData alias : indexAliases) { + AliasMetaData.Builder.toXContent(alias, builder, params); + } + } + builder.endObject(); + + builder.startObject("mappings"); + ImmutableOpenMap indexMappings = mappings.get(index); + if (indexMappings != null) { + for (final ObjectObjectCursor typeEntry : indexMappings) { + builder.field(typeEntry.key); + builder.map(typeEntry.value.sourceAsMap()); + } + } + builder.endObject(); + + builder.startObject("settings"); + Settings indexSettings = settings.get(index); + if (indexSettings != null) { + indexSettings.toXContent(builder, params); + } + builder.endObject(); + + Settings defaultIndexSettings = defaultSettings.get(index); + if (defaultIndexSettings != null && defaultIndexSettings.isEmpty() == false) { + builder.startObject("defaults"); + defaultIndexSettings.toXContent(builder, params); + builder.endObject(); + } + } + builder.endObject(); + } + } + builder.endObject(); + return builder; + } + + private static List parseAliases(XContentParser parser) throws IOException { + List indexAliases = new ArrayList<>(); + // We start at START_OBJECT since parseIndexEntry ensures that + while (parser.nextToken() != Token.END_OBJECT) { + ensureExpectedToken(Token.FIELD_NAME, parser.currentToken(), parser::getTokenLocation); + indexAliases.add(AliasMetaData.Builder.fromXContent(parser)); + } + return indexAliases; + } + + private static ImmutableOpenMap parseMappings(XContentParser parser) throws IOException { + ImmutableOpenMap.Builder indexMappings = ImmutableOpenMap.builder(); + // We start at START_OBJECT since parseIndexEntry ensures that + while (parser.nextToken() != Token.END_OBJECT) { + ensureExpectedToken(Token.FIELD_NAME, parser.currentToken(), parser::getTokenLocation); + parser.nextToken(); + if (parser.currentToken() == Token.START_OBJECT) { + String mappingType = parser.currentName(); + indexMappings.put(mappingType, new MappingMetaData(mappingType, parser.map())); + } else if (parser.currentToken() == Token.START_ARRAY) { + parser.skipChildren(); + } + } + return indexMappings.build(); + } + + private static IndexEntry parseIndexEntry(XContentParser parser) throws IOException { + List indexAliases = null; + ImmutableOpenMap indexMappings = null; + Settings indexSettings = null; + Settings indexDefaultSettings = null; + // We start at START_OBJECT since fromXContent ensures that + while (parser.nextToken() != Token.END_OBJECT) { + ensureExpectedToken(Token.FIELD_NAME, parser.currentToken(), parser::getTokenLocation); + parser.nextToken(); + if (parser.currentToken() == Token.START_OBJECT) { + switch (parser.currentName()) { + case "aliases": + indexAliases = parseAliases(parser); + break; + case "mappings": + indexMappings = parseMappings(parser); + break; + case "settings": + indexSettings = Settings.fromXContent(parser); + break; + case "defaults": + indexDefaultSettings = Settings.fromXContent(parser); + break; + default: + parser.skipChildren(); + } + } else if (parser.currentToken() == Token.START_ARRAY) { + parser.skipChildren(); + } + } + return new IndexEntry(indexAliases, indexMappings, indexSettings, indexDefaultSettings); + } + + // This is just an internal container to make stuff easier for returning + private static class IndexEntry { + List indexAliases = new ArrayList<>(); + ImmutableOpenMap indexMappings = ImmutableOpenMap.of(); + Settings indexSettings = Settings.EMPTY; + Settings indexDefaultSettings = Settings.EMPTY; + IndexEntry(List indexAliases, ImmutableOpenMap indexMappings, + Settings indexSettings, Settings indexDefaultSettings) { + if (indexAliases != null) this.indexAliases = indexAliases; + if (indexMappings != null) this.indexMappings = indexMappings; + if (indexSettings != null) this.indexSettings = indexSettings; + if (indexDefaultSettings != null) this.indexDefaultSettings = indexDefaultSettings; + } + } + + public static GetIndexResponse fromXContent(XContentParser parser) throws IOException { + ImmutableOpenMap.Builder> aliases = ImmutableOpenMap.builder(); + ImmutableOpenMap.Builder> mappings = ImmutableOpenMap.builder(); + ImmutableOpenMap.Builder settings = ImmutableOpenMap.builder(); + ImmutableOpenMap.Builder defaultSettings = ImmutableOpenMap.builder(); + List indices = new ArrayList<>(); + + if (parser.currentToken() == null) { + parser.nextToken(); + } + ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation); + parser.nextToken(); + + while (!parser.isClosed()) { + if (parser.currentToken() == Token.START_OBJECT) { + // we assume this is an index entry + String indexName = parser.currentName(); + indices.add(indexName); + IndexEntry indexEntry = parseIndexEntry(parser); + // make the order deterministic + CollectionUtil.timSort(indexEntry.indexAliases, Comparator.comparing(AliasMetaData::alias)); + aliases.put(indexName, Collections.unmodifiableList(indexEntry.indexAliases)); + mappings.put(indexName, indexEntry.indexMappings); + settings.put(indexName, indexEntry.indexSettings); + if (indexEntry.indexDefaultSettings.isEmpty() == false) { + defaultSettings.put(indexName, indexEntry.indexDefaultSettings); + } + } else if (parser.currentToken() == Token.START_ARRAY) { + parser.skipChildren(); + } else { + parser.nextToken(); + } + } + return + new GetIndexResponse( + indices.toArray(new String[0]), mappings.build(), aliases.build(), + settings.build(), defaultSettings.build() + ); + } + + @Override + public String toString() { + return Strings.toString(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o== null || getClass() != o.getClass()) return false; + GetIndexResponse that = (GetIndexResponse) o; + return Arrays.equals(indices, that.indices) && + Objects.equals(aliases, that.aliases) && + Objects.equals(mappings, that.mappings) && + Objects.equals(settings, that.settings) && + Objects.equals(defaultSettings, that.defaultSettings); + } + + @Override + public int hashCode() { + return + Objects.hash( + Arrays.hashCode(indices), + aliases, + mappings, + settings, + defaultSettings + ); } } diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/get/TransportGetIndexAction.java b/server/src/main/java/org/elasticsearch/action/admin/indices/get/TransportGetIndexAction.java index b383c02be74a7..060c345454abb 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/get/TransportGetIndexAction.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/get/TransportGetIndexAction.java @@ -36,9 +36,11 @@ import org.elasticsearch.common.collect.ImmutableOpenMap; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.settings.SettingsFilter; import org.elasticsearch.indices.IndicesService; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; +import org.elasticsearch.common.settings.IndexScopedSettings; import java.io.IOException; import java.util.List; @@ -49,14 +51,19 @@ public class TransportGetIndexAction extends TransportClusterInfoAction { private final IndicesService indicesService; + private final IndexScopedSettings indexScopedSettings; + private final SettingsFilter settingsFilter; @Inject public TransportGetIndexAction(Settings settings, TransportService transportService, ClusterService clusterService, - ThreadPool threadPool, ActionFilters actionFilters, - IndexNameExpressionResolver indexNameExpressionResolver, IndicesService indicesService) { + ThreadPool threadPool, SettingsFilter settingsFilter, ActionFilters actionFilters, + IndexNameExpressionResolver indexNameExpressionResolver, IndicesService indicesService, + IndexScopedSettings indexScopedSettings) { super(settings, GetIndexAction.NAME, transportService, clusterService, threadPool, actionFilters, GetIndexRequest::new, indexNameExpressionResolver); this.indicesService = indicesService; + this.settingsFilter = settingsFilter; + this.indexScopedSettings = indexScopedSettings; } @Override @@ -82,6 +89,7 @@ protected void doMasterOperation(final GetIndexRequest request, String[] concret ImmutableOpenMap> mappingsResult = ImmutableOpenMap.of(); ImmutableOpenMap> aliasesResult = ImmutableOpenMap.of(); ImmutableOpenMap settings = ImmutableOpenMap.of(); + ImmutableOpenMap defaultSettings = ImmutableOpenMap.of(); Feature[] features = request.features(); boolean doneAliases = false; boolean doneMappings = false; @@ -109,14 +117,21 @@ protected void doMasterOperation(final GetIndexRequest request, String[] concret case SETTINGS: if (!doneSettings) { ImmutableOpenMap.Builder settingsMapBuilder = ImmutableOpenMap.builder(); + ImmutableOpenMap.Builder defaultSettingsMapBuilder = ImmutableOpenMap.builder(); for (String index : concreteIndices) { Settings indexSettings = state.metaData().index(index).getSettings(); if (request.humanReadable()) { indexSettings = IndexMetaData.addHumanReadableSettings(indexSettings); } settingsMapBuilder.put(index, indexSettings); + if (request.includeDefaults()) { + Settings defaultIndexSettings = + settingsFilter.filter(indexScopedSettings.diff(indexSettings, Settings.EMPTY)); + defaultSettingsMapBuilder.put(index, defaultIndexSettings); + } } settings = settingsMapBuilder.build(); + defaultSettings = defaultSettingsMapBuilder.build(); doneSettings = true; } break; @@ -125,6 +140,8 @@ protected void doMasterOperation(final GetIndexRequest request, String[] concret throw new IllegalStateException("feature [" + feature + "] is not valid"); } } - listener.onResponse(new GetIndexResponse(concreteIndices, mappingsResult, aliasesResult, settings)); + listener.onResponse( + new GetIndexResponse(concreteIndices, mappingsResult, aliasesResult, settings, defaultSettings) + ); } } diff --git a/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetIndicesAction.java b/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetIndicesAction.java index e9552d4752685..04fae0f30f6bf 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetIndicesAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetIndicesAction.java @@ -19,55 +19,35 @@ package org.elasticsearch.rest.action.admin.indices; -import com.carrotsearch.hppc.cursors.ObjectObjectCursor; import org.elasticsearch.action.admin.indices.get.GetIndexRequest; -import org.elasticsearch.action.admin.indices.get.GetIndexRequest.Feature; -import org.elasticsearch.action.admin.indices.get.GetIndexResponse; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.client.node.NodeClient; -import org.elasticsearch.cluster.metadata.AliasMetaData; -import org.elasticsearch.cluster.metadata.MappingMetaData; import org.elasticsearch.common.Strings; -import org.elasticsearch.common.collect.ImmutableOpenMap; -import org.elasticsearch.common.settings.IndexScopedSettings; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.settings.SettingsFilter; -import org.elasticsearch.common.xcontent.ToXContent.Params; -import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.rest.BaseRestHandler; -import org.elasticsearch.rest.BytesRestResponse; import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestRequest; -import org.elasticsearch.rest.RestResponse; -import org.elasticsearch.rest.action.RestBuilderListener; +import org.elasticsearch.rest.action.RestToXContentListener; import java.io.IOException; -import java.util.List; import java.util.Set; import static org.elasticsearch.rest.RestRequest.Method.GET; import static org.elasticsearch.rest.RestRequest.Method.HEAD; -import static org.elasticsearch.rest.RestStatus.OK; /** * The REST handler for get index and head index APIs. */ public class RestGetIndicesAction extends BaseRestHandler { - private final IndexScopedSettings indexScopedSettings; - private final SettingsFilter settingsFilter; public RestGetIndicesAction( final Settings settings, - final RestController controller, - final IndexScopedSettings indexScopedSettings, - final SettingsFilter settingsFilter) { + final RestController controller) { super(settings); - this.indexScopedSettings = indexScopedSettings; controller.registerHandler(GET, "/{index}", this); controller.registerHandler(HEAD, "/{index}", this); - this.settingsFilter = settingsFilter; } @Override @@ -82,93 +62,10 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC getIndexRequest.indices(indices); getIndexRequest.indicesOptions(IndicesOptions.fromRequest(request, getIndexRequest.indicesOptions())); getIndexRequest.local(request.paramAsBoolean("local", getIndexRequest.local())); + getIndexRequest.masterNodeTimeout(request.paramAsTime("master_timeout", getIndexRequest.masterNodeTimeout())); getIndexRequest.humanReadable(request.paramAsBoolean("human", false)); - final boolean defaults = request.paramAsBoolean("include_defaults", false); - return channel -> client.admin().indices().getIndex(getIndexRequest, new RestBuilderListener(channel) { - - @Override - public RestResponse buildResponse(final GetIndexResponse response, final XContentBuilder builder) throws Exception { - builder.startObject(); - { - for (final String index : response.indices()) { - builder.startObject(index); - { - for (final Feature feature : getIndexRequest.features()) { - switch (feature) { - case ALIASES: - writeAliases(response.aliases().get(index), builder, request); - break; - case MAPPINGS: - writeMappings(response.mappings().get(index), builder); - break; - case SETTINGS: - writeSettings(response.settings().get(index), builder, request, defaults); - break; - default: - throw new IllegalStateException("feature [" + feature + "] is not valid"); - } - } - } - builder.endObject(); - - } - } - builder.endObject(); - - return new BytesRestResponse(OK, builder); - } - - private void writeAliases( - final List aliases, - final XContentBuilder builder, - final Params params) throws IOException { - builder.startObject("aliases"); - { - if (aliases != null) { - for (final AliasMetaData alias : aliases) { - AliasMetaData.Builder.toXContent(alias, builder, params); - } - } - } - builder.endObject(); - } - - private void writeMappings(final ImmutableOpenMap mappings, final XContentBuilder builder) - throws IOException { - builder.startObject("mappings"); - { - if (mappings != null) { - for (final ObjectObjectCursor typeEntry : mappings) { - builder.field(typeEntry.key); - builder.map(typeEntry.value.sourceAsMap()); - } - } - } - builder.endObject(); - } - - private void writeSettings( - final Settings settings, - final XContentBuilder builder, - final Params params, - final boolean defaults) throws IOException { - builder.startObject("settings"); - { - settings.toXContent(builder, params); - } - builder.endObject(); - if (defaults) { - builder.startObject("defaults"); - { - settingsFilter - .filter(indexScopedSettings.diff(settings, RestGetIndicesAction.this.settings)) - .toXContent(builder, request); - } - builder.endObject(); - } - } - - }); + getIndexRequest.includeDefaults(request.paramAsBoolean("include_defaults", false)); + return channel -> client.admin().indices().getIndex(getIndexRequest, new RestToXContentListener<>(channel)); } @Override diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/get/GetIndexActionTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/get/GetIndexActionTests.java new file mode 100644 index 0000000000000..02a98eacda0e9 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/get/GetIndexActionTests.java @@ -0,0 +1,144 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.action.admin.indices.get; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.IndicesRequest; +import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.replication.ClusterStateCreationUtils; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.settings.IndexScopedSettings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.settings.SettingsFilter; +import org.elasticsearch.common.settings.SettingsModule; +import org.elasticsearch.index.Index; +import org.elasticsearch.indices.IndicesService; +import org.elasticsearch.test.ESSingleNodeTestCase; +import org.elasticsearch.test.transport.CapturingTransport; +import org.elasticsearch.threadpool.TestThreadPool; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportService; +import org.junit.After; +import org.junit.Before; + +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +public class GetIndexActionTests extends ESSingleNodeTestCase { + + private TransportService transportService; + private ClusterService clusterService; + private IndicesService indicesService; + private ThreadPool threadPool; + private SettingsFilter settingsFilter; + private final String indexName = "test_index"; + + private TestTransportGetIndexAction getIndexAction; + + @Before + public void setUp() throws Exception { + super.setUp(); + + settingsFilter = new SettingsModule(Settings.EMPTY, Collections.emptyList(), Collections.emptyList()).getSettingsFilter(); + threadPool = new TestThreadPool("GetIndexActionTests"); + clusterService = getInstanceFromNode(ClusterService.class); + indicesService = getInstanceFromNode(IndicesService.class); + CapturingTransport capturingTransport = new CapturingTransport(); + transportService = new TransportService(clusterService.getSettings(), capturingTransport, threadPool, + TransportService.NOOP_TRANSPORT_INTERCEPTOR, + boundAddress -> clusterService.localNode(), null, Collections.emptySet()); + transportService.start(); + transportService.acceptIncomingRequests(); + getIndexAction = new GetIndexActionTests.TestTransportGetIndexAction(); + } + + @After + public void tearDown() throws Exception { + ThreadPool.terminate(threadPool, 30, TimeUnit.SECONDS); + threadPool = null; + super.tearDown(); + } + + public void testIncludeDefaults() { + GetIndexRequest defaultsRequest = new GetIndexRequest().indices(indexName).includeDefaults(true); + getIndexAction.execute(null, defaultsRequest, ActionListener.wrap( + defaultsResponse -> { + assertNotNull( + "index.refresh_interval should be set as we are including defaults", + defaultsResponse.getSetting(indexName, "index.refresh_interval") + ); + }, exception -> { + throw new AssertionError(exception); + }) + ); + } + + public void testDoNotIncludeDefaults() { + GetIndexRequest noDefaultsRequest = new GetIndexRequest().indices(indexName); + getIndexAction.execute(null, noDefaultsRequest, ActionListener.wrap( + noDefaultsResponse -> { + assertNull( + "index.refresh_interval should be null as it was never set", + noDefaultsResponse.getSetting(indexName, "index.refresh_interval") + ); + }, exception -> { + throw new AssertionError(exception); + }) + ); + } + + class TestTransportGetIndexAction extends TransportGetIndexAction { + + TestTransportGetIndexAction() { + super(Settings.EMPTY, GetIndexActionTests.this.transportService, GetIndexActionTests.this.clusterService, + GetIndexActionTests.this.threadPool, settingsFilter, new ActionFilters(Collections.emptySet()), + new GetIndexActionTests.Resolver(Settings.EMPTY), indicesService, IndexScopedSettings.DEFAULT_SCOPED_SETTINGS); + } + + @Override + protected void doMasterOperation(GetIndexRequest request, String[] concreteIndices, ClusterState state, + ActionListener listener) { + ClusterState stateWithIndex = ClusterStateCreationUtils.state(indexName, 1, 1); + super.doMasterOperation(request, concreteIndices, stateWithIndex, listener); + } + } + + static class Resolver extends IndexNameExpressionResolver { + Resolver(Settings settings) { + super(settings); + } + + @Override + public String[] concreteIndexNames(ClusterState state, IndicesRequest request) { + return request.indices(); + } + + @Override + public Index[] concreteIndices(ClusterState state, IndicesRequest request) { + Index[] out = new Index[request.indices().length]; + for (int x = 0; x < out.length; x++) { + out[x] = new Index(request.indices()[x], "_na_"); + } + return out; + } + } +} diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/get/GetIndexResponseTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/get/GetIndexResponseTests.java new file mode 100644 index 0000000000000..3991442fd5b87 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/get/GetIndexResponseTests.java @@ -0,0 +1,194 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.action.admin.indices.get; + +import org.apache.lucene.util.CollectionUtil; +import org.elasticsearch.Version; +import org.elasticsearch.action.admin.indices.alias.get.GetAliasesResponseTests; +import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponseTests; +import org.elasticsearch.cluster.metadata.AliasMetaData; +import org.elasticsearch.cluster.metadata.MappingMetaData; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.collect.ImmutableOpenMap; +import org.elasticsearch.common.io.stream.BytesStreamOutput; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.settings.IndexScopedSettings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.index.RandomCreateIndexGenerator; +import org.elasticsearch.test.AbstractStreamableXContentTestCase; +import org.junit.Assert; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.function.Predicate; + +import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_REPLICAS; +import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_SHARDS; +import static org.elasticsearch.index.IndexSettings.INDEX_REFRESH_INTERVAL_SETTING; + +public class GetIndexResponseTests extends AbstractStreamableXContentTestCase { + + /** + * The following byte response was generated from the v6.3.0 tag + */ + private static final String TEST_6_3_0_RESPONSE_BYTES = + "AQhteV9pbmRleAEIbXlfaW5kZXgBA2RvYwNkb2OePID6KURGTACqVkrLTM1JiTdUsqpWKqksSFWyUiouKcrMS1eqrQUAAAD//" + + "wMAAAABCG15X2luZGV4AgZhbGlhczEAAQJyMQECcjEGYWxpYXMyAX8jNXYiREZMAKpWKkktylWyqlaqTE0sUrIyMjA0q60FAAAA//" + + "8DAAAAAQhteV9pbmRleAIYaW5kZXgubnVtYmVyX29mX3JlcGxpY2FzAAExFmluZGV4Lm51bWJlcl9vZl9zaGFyZHMAATI="; + private static final GetIndexResponse TEST_6_3_0_RESPONSE_INSTANCE = getExpectedTest630Response(); + + @Override + protected GetIndexResponse doParseInstance(XContentParser parser) throws IOException { + return GetIndexResponse.fromXContent(parser); + } + + @Override + protected GetIndexResponse createBlankInstance() { + return new GetIndexResponse(); + } + + @Override + protected GetIndexResponse createTestInstance() { + String[] indices = generateRandomStringArray(5, 5, false, false); + ImmutableOpenMap.Builder> mappings = ImmutableOpenMap.builder(); + ImmutableOpenMap.Builder> aliases = ImmutableOpenMap.builder(); + ImmutableOpenMap.Builder settings = ImmutableOpenMap.builder(); + ImmutableOpenMap.Builder defaultSettings = ImmutableOpenMap.builder(); + IndexScopedSettings indexScopedSettings = IndexScopedSettings.DEFAULT_SCOPED_SETTINGS; + boolean includeDefaults = randomBoolean(); + for (String index: indices) { + mappings.put(index, GetMappingsResponseTests.createMappingsForIndex()); + + List aliasMetaDataList = new ArrayList<>(); + int aliasesNum = randomIntBetween(0, 3); + for (int i=0; i getRandomFieldsExcludeFilter() { + //we do not want to add new fields at the root (index-level), or inside the blocks + return + f -> f.equals("") || f.contains(".settings") || f.contains(".defaults") || f.contains(".mappings") || + f.contains(".aliases"); + } + + private static ImmutableOpenMap> getTestAliases(String indexName) { + ImmutableOpenMap.Builder> aliases = ImmutableOpenMap.builder(); + List indexAliases = new ArrayList<>(); + indexAliases.add(new AliasMetaData.Builder("alias1").routing("r1").build()); + indexAliases.add(new AliasMetaData.Builder("alias2").filter("{\"term\": {\"year\": 2016}}").build()); + aliases.put(indexName, Collections.unmodifiableList(indexAliases)); + return aliases.build(); + } + + private static ImmutableOpenMap> getTestMappings(String indexName) { + ImmutableOpenMap.Builder> mappings = ImmutableOpenMap.builder(); + ImmutableOpenMap.Builder indexMappings = ImmutableOpenMap.builder(); + try { + indexMappings.put( + "doc", + new MappingMetaData("doc", + Collections.singletonMap("field_1", Collections.singletonMap("type", "string")) + ) + ); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + mappings.put(indexName, indexMappings.build()); + return mappings.build(); + } + + private static ImmutableOpenMap getTestSettings(String indexName) { + ImmutableOpenMap.Builder settings = ImmutableOpenMap.builder(); + Settings.Builder indexSettings = Settings.builder(); + indexSettings.put(SETTING_NUMBER_OF_SHARDS, 2); + indexSettings.put(SETTING_NUMBER_OF_REPLICAS, 1); + settings.put(indexName, indexSettings.build()); + return settings.build(); + } + + private static GetIndexResponse getExpectedTest630Response() { + // The only difference between this snippet and the one used for generation TEST_6_3_0_RESPONSE_BYTES is the + // constructor for GetIndexResponse which also takes defaultSettings now. + String indexName = "my_index"; + String indices[] = { indexName }; + return + new GetIndexResponse( + indices, getTestMappings(indexName), getTestAliases(indexName), getTestSettings(indexName), + ImmutableOpenMap.of() + ); + } + + private static GetIndexResponse getResponseWithDefaultSettings() { + String indexName = "my_index"; + String indices[] = { indexName }; + ImmutableOpenMap.Builder defaultSettings = ImmutableOpenMap.builder(); + Settings.Builder indexDefaultSettings = Settings.builder(); + indexDefaultSettings.put(INDEX_REFRESH_INTERVAL_SETTING.getKey(), "1s"); + defaultSettings.put(indexName, indexDefaultSettings.build()); + return + new GetIndexResponse( + indices, getTestMappings(indexName), getTestAliases(indexName), getTestSettings(indexName), + defaultSettings.build() + ); + } + + public void testCanDecode622Response() throws IOException { + StreamInput si = StreamInput.wrap(Base64.getDecoder().decode(TEST_6_3_0_RESPONSE_BYTES)); + si.setVersion(Version.V_6_3_0); + GetIndexResponse response = new GetIndexResponse(); + response.readFrom(si); + + Assert.assertEquals(TEST_6_3_0_RESPONSE_INSTANCE, response); + } + + public void testCanOutput622Response() throws IOException { + GetIndexResponse responseWithExtraFields = getResponseWithDefaultSettings(); + BytesStreamOutput bso = new BytesStreamOutput(); + bso.setVersion(Version.V_6_3_0); + responseWithExtraFields.writeTo(bso); + String base64OfResponse = Base64.getEncoder().encodeToString(BytesReference.toBytes(bso.bytes())); + + Assert.assertEquals(TEST_6_3_0_RESPONSE_BYTES, base64OfResponse); + } + +} diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/mapping/get/GetMappingsResponseTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/mapping/get/GetMappingsResponseTests.java index 0fa5ca075fa8d..91c7841868393 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/indices/mapping/get/GetMappingsResponseTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/mapping/get/GetMappingsResponseTests.java @@ -80,8 +80,7 @@ protected GetMappingsResponse mutateInstance(GetMappingsResponse instance) throw return mutate(instance); } - @Override - protected GetMappingsResponse createTestInstance() { + public static ImmutableOpenMap createMappingsForIndex() { // rarely have no types int typeCount = rarely() ? 0 : scaledRandomIntBetween(1, 3); List typeMappings = new ArrayList<>(typeCount); @@ -104,8 +103,13 @@ protected GetMappingsResponse createTestInstance() { } ImmutableOpenMap.Builder typeBuilder = ImmutableOpenMap.builder(); typeMappings.forEach(mmd -> typeBuilder.put(mmd.type(), mmd)); + return typeBuilder.build(); + } + + @Override + protected GetMappingsResponse createTestInstance() { ImmutableOpenMap.Builder> indexBuilder = ImmutableOpenMap.builder(); - indexBuilder.put("index-" + randomAlphaOfLength(5), typeBuilder.build()); + indexBuilder.put("index-" + randomAlphaOfLength(5), createMappingsForIndex()); GetMappingsResponse resp = new GetMappingsResponse(indexBuilder.build()); logger.debug("--> created: {}", resp); return resp;