diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java index fcea9044856c5..1e7566e89a42d 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java @@ -1223,7 +1223,7 @@ public void testMultiSearch() throws IOException { }; MultiSearchRequest.readMultiLineFormat(new BytesArray(EntityUtils.toByteArray(request.getEntity())), REQUEST_BODY_CONTENT_TYPE.xContent(), consumer, null, multiSearchRequest.indicesOptions(), null, null, null, - xContentRegistry(), true); + xContentRegistry(), true, key -> false); assertEquals(requests, multiSearchRequest.requests()); } diff --git a/docs/src/test/java/org/elasticsearch/smoketest/DocsClientYamlTestSuiteIT.java b/docs/src/test/java/org/elasticsearch/smoketest/DocsClientYamlTestSuiteIT.java index 069945f1b7474..6c9cc7c074ace 100644 --- a/docs/src/test/java/org/elasticsearch/smoketest/DocsClientYamlTestSuiteIT.java +++ b/docs/src/test/java/org/elasticsearch/smoketest/DocsClientYamlTestSuiteIT.java @@ -74,7 +74,7 @@ public static Iterable parameters() throws Exception { entries.add(new NamedXContentRegistry.Entry(ExecutableSection.class, new ParseField("compare_analyzers"), CompareAnalyzers::parse)); NamedXContentRegistry executableSectionRegistry = new NamedXContentRegistry(entries); - return ESClientYamlSuiteTestCase.createParameters(executableSectionRegistry); + return ESClientYamlSuiteTestCase.createParameters(executableSectionRegistry, TESTS_PATH); } @Override diff --git a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentBuilder.java b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentBuilder.java index 20fde0891b6f8..c35f59c53cd6d 100644 --- a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentBuilder.java +++ b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentBuilder.java @@ -165,6 +165,8 @@ public interface HumanReadableTransformer { */ private boolean humanReadable = false; + private byte compatibleMajorVersion; + /** * Constructs a new builder using the provided XContent and an OutputStream. Make sure * to call {@link #close()} when the builder is done with. @@ -998,6 +1000,16 @@ public XContentBuilder copyCurrentStructure(XContentParser parser) throws IOExce return this; } + public XContentBuilder setCompatibleMajorVersion(byte compatibleMajorVersion){ + this.compatibleMajorVersion = compatibleMajorVersion; + return this; + } + + public byte getCompatibleMajorVersion() { + return compatibleMajorVersion; + } + + @Override public void flush() throws IOException { generator.flush(); diff --git a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java index 606284f046244..42129e4f5e4bb 100644 --- a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java +++ b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentType.java @@ -26,6 +26,8 @@ import java.util.Locale; import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * The content type of {@link org.elasticsearch.common.xcontent.XContent}. @@ -114,13 +116,19 @@ public XContent xContent() { } }; + private static final Pattern COMPATIBLE_API_HEADER_PATTERN = Pattern.compile( + "(application|text)/(vnd.elasticsearch\\+)?([^;]+)(\\s*;\\s*compatible-with=(\\d+))?", + Pattern.CASE_INSENSITIVE); + /** * Accepts either a format string, which is equivalent to {@link XContentType#shortName()} or a media type that optionally has * parameters and attempts to match the value to an {@link XContentType}. The comparisons are done in lower case format and this method * also supports a wildcard accept for {@code application/*}. This method can be used to parse the {@code Accept} HTTP header or a * format query string parameter. This method will return {@code null} if no match is found */ - public static XContentType fromMediaTypeOrFormat(String mediaType) { + public static XContentType fromMediaTypeOrFormat(String mediaTypeHeaderValue) { + String mediaType = parseMediaType(mediaTypeHeaderValue); + if (mediaType == null) { return null; } @@ -130,7 +138,7 @@ public static XContentType fromMediaTypeOrFormat(String mediaType) { } } final String lowercaseMediaType = mediaType.toLowerCase(Locale.ROOT); - if (lowercaseMediaType.startsWith("application/*")) { + if (lowercaseMediaType.startsWith("application/*") || lowercaseMediaType.equals("*/*")) { return JSON; } @@ -142,7 +150,9 @@ public static XContentType fromMediaTypeOrFormat(String mediaType) { * The provided media type should not include any parameters. This method is suitable for parsing part of the {@code Content-Type} * HTTP header. This method will return {@code null} if no match is found */ - public static XContentType fromMediaType(String mediaType) { + public static XContentType fromMediaType(String mediaTypeHeaderValue) { + String mediaType = parseMediaType(mediaTypeHeaderValue); + final String lowercaseMediaType = Objects.requireNonNull(mediaType, "mediaType cannot be null").toLowerCase(Locale.ROOT); for (XContentType type : values()) { if (type.mediaTypeWithoutParameters().equals(lowercaseMediaType)) { @@ -157,6 +167,28 @@ public static XContentType fromMediaType(String mediaType) { return null; } + //public scope needed for text formats hack + public static String parseMediaType(String mediaType) { + if (mediaType != null) { + Matcher matcher = COMPATIBLE_API_HEADER_PATTERN.matcher(mediaType); + if (matcher.find()) { + return (matcher.group(1) + "/" + matcher.group(3)).toLowerCase(Locale.ROOT); + } + } + + return mediaType; + } + + public static String parseVersion(String mediaType){ + if(mediaType != null){ + Matcher matcher = COMPATIBLE_API_HEADER_PATTERN.matcher(mediaType); + if (matcher.find() && "vnd.elasticsearch+".equalsIgnoreCase(matcher.group(2))) { + + return matcher.group(5); + } + } + return null; + } private static boolean isSameMediaTypeOrFormatAs(String stringType, XContentType type) { return type.mediaTypeWithoutParameters().equalsIgnoreCase(stringType) || stringType.toLowerCase(Locale.ROOT).startsWith(type.mediaTypeWithoutParameters().toLowerCase(Locale.ROOT) + ";") || diff --git a/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateAction.java b/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateAction.java index 52052d5ad67ac..1896cd0eec143 100644 --- a/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateAction.java +++ b/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateAction.java @@ -32,6 +32,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Function; import static java.util.Arrays.asList; import static org.elasticsearch.rest.RestRequest.Method.GET; @@ -49,7 +50,7 @@ public class RestMultiSearchTemplateAction extends BaseRestHandler { } - private final boolean allowExplicitIndex; + protected final boolean allowExplicitIndex; public RestMultiSearchTemplateAction(Settings settings) { this.allowExplicitIndex = MULTI_ALLOW_EXPLICIT_INDEX.get(settings); @@ -79,6 +80,19 @@ public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client * Parses a {@link RestRequest} body and returns a {@link MultiSearchTemplateRequest} */ public static MultiSearchTemplateRequest parseRequest(RestRequest restRequest, boolean allowExplicitIndex) throws IOException { + return parseRequest(restRequest,allowExplicitIndex, k->false); + } + + /** + * Parses a {@link RestRequest} body and returns a {@link MultiSearchTemplateRequest} + * @param typeConsumer - A function used to validate if a provided xContent key is allowed. + * This is useful for xContent compatibility to determine + * if a key is allowed to be present in version agnostic manner. + * The provided function should return false if the key is not allowed. + */ + public static MultiSearchTemplateRequest parseRequest(RestRequest restRequest, + boolean allowExplicitIndex, + Function typeConsumer) throws IOException { MultiSearchTemplateRequest multiRequest = new MultiSearchTemplateRequest(); if (restRequest.hasParam("max_concurrent_searches")) { multiRequest.maxConcurrentSearchRequests(restRequest.paramAsInt("max_concurrent_searches", 0)); @@ -94,7 +108,7 @@ public static MultiSearchTemplateRequest parseRequest(RestRequest restRequest, b throw new IllegalArgumentException("Malformed search template"); } RestSearchAction.checkRestTotalHits(restRequest, searchRequest); - }); + }, typeConsumer); return multiRequest; } diff --git a/modules/rest-compatibility/build.gradle b/modules/rest-compatibility/build.gradle new file mode 100644 index 0000000000000..a4b9b20d046a5 --- /dev/null +++ b/modules/rest-compatibility/build.gradle @@ -0,0 +1,31 @@ +/* + * 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. + */ + +esplugin { + description 'Adds a compatiblity layer for the prior major versions REST API' + classname 'org.elasticsearch.compat.RestCompatPlugin' +} + +dependencies { + implementation project(':modules:lang-mustache') + implementation project(':modules:reindex') +} + +integTest.enabled = false +test.enabled = true diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/compat/RestCompatPlugin.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/compat/RestCompatPlugin.java new file mode 100644 index 0000000000000..d45d60cff2a52 --- /dev/null +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/compat/RestCompatPlugin.java @@ -0,0 +1,106 @@ +/* + * 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.compat; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.Version; +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.cluster.node.DiscoveryNodes; +import org.elasticsearch.common.settings.ClusterSettings; +import org.elasticsearch.common.settings.IndexScopedSettings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.settings.SettingsFilter; +import org.elasticsearch.index.reindex.RestDeleteByQueryActionV7; +import org.elasticsearch.index.reindex.RestUpdateByQueryActionV7; +import org.elasticsearch.plugins.ActionPlugin; +import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.rest.RestController; +import org.elasticsearch.rest.RestHandler; +import org.elasticsearch.rest.action.admin.indices.RestCreateIndexActionV7; +import org.elasticsearch.rest.action.document.RestDeleteActionV7; +import org.elasticsearch.rest.action.document.RestGetActionV7; +import org.elasticsearch.rest.action.document.RestIndexActionV7; +import org.elasticsearch.rest.action.document.RestMultiTermVectorsActionV7; +import org.elasticsearch.rest.action.document.RestTermVectorsActionV7; +import org.elasticsearch.rest.action.document.RestUpdateActionV7; +import org.elasticsearch.rest.action.search.RestMultiSearchActionV7; +import org.elasticsearch.rest.action.search.RestSearchActionV7; +import org.elasticsearch.script.mustache.RestMultiSearchTemplateActionV7; +import org.elasticsearch.script.mustache.RestSearchTemplateActionV7; + +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.function.Supplier; + +public class RestCompatPlugin extends Plugin implements ActionPlugin { + Logger log = LogManager.getLogger(RestCompatPlugin.class); + @Override + public List getRestHandlers( + Settings settings, + RestController restController, + ClusterSettings clusterSettings, + IndexScopedSettings indexScopedSettings, + SettingsFilter settingsFilter, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier nodesInCluster + ) { + boolean compatibilityEnabled = Boolean.parseBoolean(settings.get("compat.setting")); + log.warn(" compat setting "+compatibilityEnabled); + if (compatibilityEnabled && Version.CURRENT.major == 8) { + return validateCompatibleHandlers( + 7, + new RestDeleteByQueryActionV7(), + new RestUpdateByQueryActionV7(), + new RestCreateIndexActionV7(), + new RestGetActionV7(), + new RestIndexActionV7.CompatibleRestIndexAction(), + new RestIndexActionV7.CompatibleCreateHandler(), + new RestIndexActionV7.CompatibleAutoIdHandler(nodesInCluster), + new RestTermVectorsActionV7(), + new RestMultiTermVectorsActionV7(), + new RestSearchActionV7(), + new RestMultiSearchActionV7(settings), + new RestSearchTemplateActionV7(), + new RestMultiSearchTemplateActionV7(settings), + new RestDeleteActionV7(), + new RestUpdateActionV7() + ); + } + return Collections.emptyList(); + } + + // default scope for testing + List validateCompatibleHandlers(int expectedVersion, RestHandler... handlers) { + for (RestHandler handler : handlers) { + if (handler.compatibleWithVersion().major != expectedVersion) { + String msg = String.format( + Locale.ROOT, + "Handler %s is of incorrect version %s.", + handler.getClass().getSimpleName(), + handler.compatibleWithVersion() + ); + throw new IllegalStateException(msg); + } + } + return List.of(handlers); + } +} diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/compat/TypeConsumer.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/compat/TypeConsumer.java new file mode 100644 index 0000000000000..f7c0e9b9e9283 --- /dev/null +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/compat/TypeConsumer.java @@ -0,0 +1,53 @@ +/* + * 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.compat; + +import org.elasticsearch.common.Strings; +import org.elasticsearch.rest.RestRequest; + +import java.util.Set; +import java.util.function.Function; + +public class TypeConsumer implements Function { + + private final RestRequest request; + private final Set fieldNames; + private boolean foundTypeInBody = false; + + public TypeConsumer(RestRequest request, String... fieldNames) { + this.request = request; + this.fieldNames = Set.of(fieldNames); + } + + @Override + public Boolean apply(String fieldName) { + if (fieldNames.contains(fieldName)) { + foundTypeInBody = true; + return true; + } + return false; + } + + public boolean hasTypes() { + // TODO can params be types too? or _types? + String[] types = Strings.splitStringByCommaToArray(request.param("type")); + return types.length > 0 || foundTypeInBody; + } +} diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/index/reindex/RestDeleteByQueryActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/index/reindex/RestDeleteByQueryActionV7.java new file mode 100644 index 0000000000000..9fdba5d9edbc0 --- /dev/null +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/index/reindex/RestDeleteByQueryActionV7.java @@ -0,0 +1,59 @@ +/* + * 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.index.reindex; + +import org.elasticsearch.Version; +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.logging.DeprecationLogger; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.action.search.RestSearchActionV7; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.rest.RestRequest.Method.POST; + +public class RestDeleteByQueryActionV7 extends RestDeleteByQueryAction { + private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(RestDeleteByQueryActionV7.class); + + @Override + public List routes() { + return List.of(new Route(POST, "/{index}/{type}/_delete_by_query")); + } + + @Override + public String getName() { + return super.getName() + "_v7"; + } + + @Override + public Version compatibleWithVersion() { + return Version.V_7_0_0; + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + if (request.hasParam("type")) { + deprecationLogger.deprecate("search_with_types", RestSearchActionV7.TYPES_DEPRECATION_MESSAGE); + request.param("type"); + } + return super.prepareRequest(request, client); + } +} diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/index/reindex/RestUpdateByQueryActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/index/reindex/RestUpdateByQueryActionV7.java new file mode 100644 index 0000000000000..29486286597c5 --- /dev/null +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/index/reindex/RestUpdateByQueryActionV7.java @@ -0,0 +1,60 @@ +/* + * 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.index.reindex; + +import org.elasticsearch.Version; +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.logging.DeprecationLogger; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.action.search.RestSearchActionV7; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.rest.RestRequest.Method.POST; + +public class RestUpdateByQueryActionV7 extends RestUpdateByQueryAction { + + private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(RestUpdateByQueryActionV7.class); + + @Override + public List routes() { + return List.of(new Route(POST, "/{index}/{type}/_update_by_query")); + } + + @Override + public String getName() { + return super.getName() + "_v7"; + } + + @Override + public Version compatibleWithVersion() { + return Version.V_7_0_0; + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + if (request.hasParam("type")) { + deprecationLogger.deprecate("search_with_types", RestSearchActionV7.TYPES_DEPRECATION_MESSAGE); + request.param("type"); + } + return super.prepareRequest(request, client); + } +} diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/admin/indices/RestCreateIndexActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/admin/indices/RestCreateIndexActionV7.java new file mode 100644 index 0000000000000..26861e0b5cd1c --- /dev/null +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/admin/indices/RestCreateIndexActionV7.java @@ -0,0 +1,119 @@ +/* + * 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.rest.action.admin.indices; + +import org.elasticsearch.Version; +import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; +import org.elasticsearch.action.support.ActiveShardCount; +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.xcontent.LoggingDeprecationHandler; +import org.elasticsearch.common.xcontent.XContentHelper; +import org.elasticsearch.index.mapper.MapperService; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.action.RestToXContentListener; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static java.util.Collections.singletonMap; + +public class RestCreateIndexActionV7 extends RestCreateIndexAction { + + /** + * Parameter that controls whether certain REST apis should include type names in their requests. + */ + public static final String INCLUDE_TYPE_NAME_PARAMETER = "include_type_name"; + public static final boolean DEFAULT_INCLUDE_TYPE_NAME_POLICY = false; + + @Override + public String getName() { + return super.getName() + "_v7"; + } + + @Override + public Version compatibleWithVersion() { + return Version.V_7_0_0; + } + + @Override + public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { + CreateIndexRequest createIndexRequest = prepareV7Request(request); + return channel -> client.admin().indices().create(createIndexRequest, new RestToXContentListener<>(channel)); + } + + // default scope for testing + CreateIndexRequest prepareV7Request(RestRequest request) { + CreateIndexRequest createIndexRequest = new CreateIndexRequest(request.param("index")); + + if (request.hasContent()) { + Map sourceAsMap = XContentHelper.convertToMap(request.requiredContent(), false, request.getXContentType()).v2(); + + request.param(INCLUDE_TYPE_NAME_PARAMETER);// just consume, it is always replaced with _doc + sourceAsMap = prepareMappingsV7(sourceAsMap, request); + + createIndexRequest.source(sourceAsMap, LoggingDeprecationHandler.INSTANCE); + } + + createIndexRequest.timeout(request.paramAsTime("timeout", createIndexRequest.timeout())); + createIndexRequest.masterNodeTimeout(request.paramAsTime("master_timeout", createIndexRequest.masterNodeTimeout())); + createIndexRequest.waitForActiveShards(ActiveShardCount.parseString(request.param("wait_for_active_shards"))); + return createIndexRequest; + } + + static Map prepareMappingsV7(Map source, RestRequest request) { + final boolean includeTypeName = request.paramAsBoolean(INCLUDE_TYPE_NAME_PARAMETER, false); + + @SuppressWarnings("unchecked") + Map mappings = (Map) source.get("mappings"); + + if (includeTypeName && mappings.size() == 1) { + Map newSource = new HashMap<>(); + + String typeName = mappings.keySet().iterator().next(); + @SuppressWarnings("unchecked") + Map typedMappings = (Map) mappings.get(typeName); + + // no matter what the type was, replace it with _doc, because the internal representation still uses single type `_doc`. + newSource.put("mappings", Collections.singletonMap(MapperService.SINGLE_MAPPING_NAME, typedMappings)); + return newSource; + } else { + return prepareMappings(source); + } + } + + static Map prepareMappings(Map source) { + if (source.containsKey("mappings") == false || (source.get("mappings") instanceof Map) == false) { + return source; + } + + Map newSource = new HashMap<>(source); + + @SuppressWarnings("unchecked") + Map mappings = (Map) source.get("mappings"); + if (MapperService.isMappingSourceTyped(MapperService.SINGLE_MAPPING_NAME, mappings)) { + throw new IllegalArgumentException("The mapping definition cannot be nested under a type"); + } + + newSource.put("mappings", singletonMap(MapperService.SINGLE_MAPPING_NAME, mappings)); + return newSource; + } +} diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetFieldMappingActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetFieldMappingActionV7.java new file mode 100644 index 0000000000000..71817de9c5345 --- /dev/null +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetFieldMappingActionV7.java @@ -0,0 +1,88 @@ +/* + * 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.rest.action.admin.indices; + +import org.elasticsearch.Version; +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.logging.DeprecationLogger; +import org.elasticsearch.rest.RestRequest; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.rest.RestRequest.Method.GET; + +public class RestGetFieldMappingActionV7 extends RestGetFieldMappingAction { + + private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(RestGetFieldMappingActionV7.class); + public static final String TYPES_DEPRECATION_MESSAGE = "[types removal] Using include_type_name in get " + + "field mapping requests is deprecated. The parameter will be removed in the next major version."; + + @Override + public List routes() { + return List.of( + new Route(GET, "/_mapping/field/{fields}"), + new Route(GET, "/{index}/_mapping/field/{fields}"), + + new Route(GET, "/_mapping/{type}/field/{fields}"), + new Route(GET, "/{index}/{type}/_mapping/field/{fields}"), + new Route(GET, "/{index}/_mapping/{type}/field/{fields}") + ); + } + + @Override + public String getName() { + return "get_field_mapping_action_v7"; + } + + @Override + public Version compatibleWithVersion() { + return Version.V_7_0_0; + } + + @Override + public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { + final String[] types = request.paramAsStringArrayOrEmptyIfAll("type"); + + boolean includeTypeName = request.paramAsBoolean( + RestCreateIndexActionV7.INCLUDE_TYPE_NAME_PARAMETER, + RestCreateIndexActionV7.DEFAULT_INCLUDE_TYPE_NAME_POLICY + ); + if (includeTypeName == false && types.length > 0) { + throw new IllegalArgumentException("Types cannot be specified unless include_type_name" + " is set to true."); + } + if (request.hasParam(RestCreateIndexActionV7.INCLUDE_TYPE_NAME_PARAMETER)) { + request.param(RestCreateIndexActionV7.INCLUDE_TYPE_NAME_PARAMETER); + deprecationLogger.deprecate("get_field_mapping_with_types", TYPES_DEPRECATION_MESSAGE); + // todo compatible log about using INCLUDE_TYPE_NAME_PARAMETER + } + if (types.length > 0) { + // todo compatible log about using types in path + } + if (request.hasParam("local")) { + request.param("local"); + deprecationLogger.deprecate( + "get_field_mapping_local", + "Use [local] in get field mapping requests is deprecated. " + "The parameter will be removed in the next major version" + ); + } + return super.prepareRequest(request, client); + } +} diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetMappingActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetMappingActionV7.java new file mode 100644 index 0000000000000..17a23a488dccf --- /dev/null +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetMappingActionV7.java @@ -0,0 +1,86 @@ +/* + * 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.rest.action.admin.indices; + +import org.elasticsearch.Version; +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.logging.DeprecationLogger; +import org.elasticsearch.rest.RestRequest; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.rest.RestRequest.Method.GET; +import static org.elasticsearch.rest.RestRequest.Method.HEAD; +import static org.elasticsearch.rest.action.admin.indices.RestCreateIndexActionV7.DEFAULT_INCLUDE_TYPE_NAME_POLICY; +import static org.elasticsearch.rest.action.admin.indices.RestCreateIndexActionV7.INCLUDE_TYPE_NAME_PARAMETER; + +public class RestGetMappingActionV7 extends RestGetMappingAction { + private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(RestGetMappingActionV7.class); + public static final String TYPES_DEPRECATION_MESSAGE = "[types removal] Using include_type_name in get" + + " mapping requests is deprecated. The parameter will be removed in the next major version."; + + @Override + public List routes() { + return List.of( + new Route(GET, "/{index}/{type}/_mapping"), + new Route(GET, "/{index}/_mappings/{type}"), + new Route(GET, "/{index}/_mapping/{type}"), + new Route(HEAD, "/{index}/_mapping/{type}"), + new Route(GET, "/_mapping/{type}"), + new Route(GET, "/_mapping"), + new Route(GET, "/_mappings"), + new Route(GET, "/{index}/_mapping"), + new Route(GET, "/{index}/_mappings") + ); + } + + @Override + public String getName() { + return "get_mapping_action_v7"; + } + + @Override + public Version compatibleWithVersion() { + return Version.V_7_0_0; + } + + @Override + public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { + final String[] types = request.paramAsStringArrayOrEmptyIfAll("type"); + boolean includeTypeName = request.paramAsBoolean(INCLUDE_TYPE_NAME_PARAMETER, DEFAULT_INCLUDE_TYPE_NAME_POLICY); + + if (request.method().equals(HEAD)) { + deprecationLogger.deprecate("get_mapping_with_types", "Type exists requests are deprecated, as types have been deprecated."); + } else if (includeTypeName == false && types.length > 0) { + throw new IllegalArgumentException( + "Types cannot be provided in get mapping requests, unless" + " include_type_name is set to true." + ); + } + if (request.hasParam(INCLUDE_TYPE_NAME_PARAMETER)) { + request.paramAsBoolean(INCLUDE_TYPE_NAME_PARAMETER, DEFAULT_INCLUDE_TYPE_NAME_POLICY); + deprecationLogger.deprecate("get_mapping_with_types", TYPES_DEPRECATION_MESSAGE); + } + if (types.length > 0) { + // todo compatible log about using types in path + } + return super.prepareRequest(request, client); + } +} diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/admin/indices/RestPutMappingActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/admin/indices/RestPutMappingActionV7.java new file mode 100644 index 0000000000000..ace1bbf3b548d --- /dev/null +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/admin/indices/RestPutMappingActionV7.java @@ -0,0 +1,111 @@ +/* + * 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.rest.action.admin.indices; + +import org.elasticsearch.Version; +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.logging.DeprecationLogger; +import org.elasticsearch.common.xcontent.XContentHelper; +import org.elasticsearch.index.mapper.MapperService; +import org.elasticsearch.rest.RestRequest; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static org.elasticsearch.rest.RestRequest.Method.POST; +import static org.elasticsearch.rest.RestRequest.Method.PUT; +import static org.elasticsearch.rest.action.admin.indices.RestCreateIndexActionV7.DEFAULT_INCLUDE_TYPE_NAME_POLICY; +import static org.elasticsearch.rest.action.admin.indices.RestCreateIndexActionV7.INCLUDE_TYPE_NAME_PARAMETER; + +public class RestPutMappingActionV7 extends RestPutMappingAction { + private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(RestPutMappingActionV7.class); + public static final String TYPES_DEPRECATION_MESSAGE = "[types removal] Using include_type_name in put " + + "mapping requests is deprecated. The parameter will be removed in the next major version."; + + @Override + public List routes() { + return List.of( + new Route(PUT, "/{index}/{type}/_mapping"), + new Route(PUT, "/{index}/_mapping/{type}"), + new Route(PUT, "/_mapping/{type}"), + + new Route(POST, "/{index}/{type}/_mapping"), + new Route(POST, "/{index}/_mapping/{type}"), + new Route(POST, "/_mapping/{type}"), + + // register the same paths, but with plural form _mappings + new Route(PUT, "/{index}/{type}/_mappings"), + new Route(PUT, "/{index}/_mappings/{type}"), + new Route(PUT, "/_mappings/{type}"), + + new Route(POST, "/{index}/{type}/_mappings"), + new Route(POST, "/{index}/_mappings/{type}"), + new Route(POST, "/_mappings/{type}"), + + // no types in path, but type can be provided in body + new Route(POST, "/{index}/_mapping/"), + new Route(PUT, "/{index}/_mapping/"), + new Route(POST, "/{index}/_mappings/"), + new Route(PUT, "/{index}/_mappings/") + ); + } + + @Override + public String getName() { + return "put_mapping_action_v7"; + } + + @Override + public Version compatibleWithVersion() { + return Version.V_7_0_0; + } + + @Override + public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { + final boolean includeTypeName = request.paramAsBoolean(INCLUDE_TYPE_NAME_PARAMETER, DEFAULT_INCLUDE_TYPE_NAME_POLICY); + + String type = request.param("type"); + Map sourceAsMap = XContentHelper.convertToMap(request.requiredContent(), false, request.getXContentType()).v2(); + if (includeTypeName == false + && (type != null || MapperService.isMappingSourceTyped(MapperService.SINGLE_MAPPING_NAME, sourceAsMap))) { + throw new IllegalArgumentException( + "Types cannot be provided in put mapping requests, unless the include_type_name parameter is set to true." + ); + } + if (request.hasParam(INCLUDE_TYPE_NAME_PARAMETER)) { + deprecationLogger.deprecate("put_mapping_with_types", TYPES_DEPRECATION_MESSAGE); + } + if (includeTypeName) { + sourceAsMap = prepareMappingsV7(sourceAsMap, request); + } + return super.sendPutMappingRequest(request, client, sourceAsMap); + } + + private Map prepareMappingsV7(Map mappings, RestRequest request) { + String typeName = mappings.keySet().iterator().next(); + @SuppressWarnings("unchecked") + Map typedMappings = (Map) mappings.get(typeName); + + // no matter what the type was, replace it with _doc, because the internal representation still uses single type `_doc`. + return Collections.singletonMap(MapperService.SINGLE_MAPPING_NAME, typedMappings); + } +} diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestDeleteActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestDeleteActionV7.java new file mode 100644 index 0000000000000..efc5150ab02c3 --- /dev/null +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestDeleteActionV7.java @@ -0,0 +1,62 @@ +/* + * 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.rest.action.document; + +import org.elasticsearch.Version; +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.logging.DeprecationLogger; +import org.elasticsearch.rest.RestRequest; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.rest.RestRequest.Method.DELETE; + +public class RestDeleteActionV7 extends RestDeleteAction { + private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(RestDeleteActionV7.class); + public static final String TYPES_DEPRECATION_MESSAGE = "[types removal] Specifying types in " + + "document index requests is deprecated, use the /{index}/_doc/{id} endpoint instead."; + + @Override + public List routes() { + return List.of(new Route(DELETE, "/{index}/{type}/{id}")); + } + + @Override + public String getName() { + return "document_delete_action_v7"; + } + + @Override + public Version compatibleWithVersion() { + return Version.V_7_0_0; + } + + @Override + public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { + if (request.hasParam("type")) { + request.param("type"); + deprecationLogger.deprecate("delete_with_types", TYPES_DEPRECATION_MESSAGE); + // todo compatible log + } + + return super.prepareRequest(request, client); + } +} diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestGetActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestGetActionV7.java new file mode 100644 index 0000000000000..a75fc46d2428e --- /dev/null +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestGetActionV7.java @@ -0,0 +1,60 @@ +/* + * 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.rest.action.document; + +import org.elasticsearch.Version; +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.logging.DeprecationLogger; +import org.elasticsearch.rest.RestRequest; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.rest.RestRequest.Method.GET; +import static org.elasticsearch.rest.RestRequest.Method.HEAD; + +public class RestGetActionV7 extends RestGetAction { + + private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(RestGetActionV7.class); + static final String TYPES_DEPRECATION_MESSAGE = "[types removal] Specifying types in " + + "document get requests is deprecated, use the /{index}/_doc/{id} endpoint instead."; + + @Override + public String getName() { + return super.getName() + "_v7"; + } + + @Override + public List routes() { + return List.of(new Route(GET, "/{index}/{type}/{id}"), new Route(HEAD, "/{index}/{type}/{id}")); + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, final NodeClient client) throws IOException { + deprecationLogger.deprecate("get_with_types", TYPES_DEPRECATION_MESSAGE); + request.param("type"); + return super.prepareRequest(request, client); + } + + @Override + public Version compatibleWithVersion() { + return Version.V_7_0_0; + } +} diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestIndexActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestIndexActionV7.java new file mode 100644 index 0000000000000..134deea9ff9db --- /dev/null +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestIndexActionV7.java @@ -0,0 +1,123 @@ +/* + * 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.rest.action.document; + +import org.elasticsearch.Version; +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.cluster.node.DiscoveryNodes; +import org.elasticsearch.common.logging.DeprecationLogger; +import org.elasticsearch.rest.RestRequest; + +import java.io.IOException; +import java.util.List; +import java.util.function.Supplier; + +import static java.util.Collections.singletonList; +import static org.elasticsearch.rest.RestRequest.Method.POST; +import static org.elasticsearch.rest.RestRequest.Method.PUT; + +public class RestIndexActionV7 { + static final String TYPES_DEPRECATION_MESSAGE = "[types removal] Specifying types in document " + + "index requests is deprecated, use the typeless endpoints instead (/{index}/_doc/{id}, /{index}/_doc, " + + "or /{index}/_create/{id})."; + private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(RestIndexActionV7.class); + + private static void logDeprecationMessage() { + deprecationLogger.deprecate("index_with_types", TYPES_DEPRECATION_MESSAGE); + } + + public static class CompatibleRestIndexAction extends RestIndexAction { + @Override + public String getName() { + return super.getName() + "_v7"; + } + + @Override + public List routes() { + return List.of(new Route(POST, "/{index}/{type}/{id}"), new Route(PUT, "/{index}/{type}/{id}")); + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, final NodeClient client) throws IOException { + logDeprecationMessage(); + request.param("type"); + return super.prepareRequest(request, client); + } + + @Override + public Version compatibleWithVersion() { + return Version.V_7_0_0; + } + } + + public static class CompatibleCreateHandler extends RestIndexAction.CreateHandler { + + @Override + public String getName() { + return "document_create_action_v7"; + } + + @Override + public List routes() { + return List.of(new Route(POST, "/{index}/{type}/{id}/_create"), new Route(PUT, "/{index}/{type}/{id}/_create")); + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, final NodeClient client) throws IOException { + logDeprecationMessage(); + request.param("type"); + return super.prepareRequest(request, client); + } + + @Override + public Version compatibleWithVersion() { + return Version.V_7_0_0; + } + } + + public static final class CompatibleAutoIdHandler extends RestIndexAction.AutoIdHandler { + + public CompatibleAutoIdHandler(Supplier nodesInCluster) { + super(nodesInCluster); + } + + @Override + public String getName() { + return "document_create_action_auto_id_v7"; + } + + @Override + public List routes() { + return singletonList(new Route(POST, "/{index}/{type}")); + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, final NodeClient client) throws IOException { + logDeprecationMessage(); + request.param("type"); + return super.prepareRequest(request, client); + } + + @Override + public Version compatibleWithVersion() { + return Version.V_7_0_0; + } + } +} diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestMultiTermVectorsActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestMultiTermVectorsActionV7.java new file mode 100644 index 0000000000000..d4df9c2a9baab --- /dev/null +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestMultiTermVectorsActionV7.java @@ -0,0 +1,81 @@ +/* + * 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.rest.action.document; + +import org.elasticsearch.Version; +import org.elasticsearch.action.termvectors.MultiTermVectorsRequest; +import org.elasticsearch.action.termvectors.TermVectorsRequest; +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.logging.DeprecationLogger; +import org.elasticsearch.compat.TypeConsumer; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.action.RestToXContentListener; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.rest.RestRequest.Method.GET; +import static org.elasticsearch.rest.RestRequest.Method.POST; + +public class RestMultiTermVectorsActionV7 extends RestMultiTermVectorsAction { + private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(RestMultiTermVectorsActionV7.class); + static final String TYPES_DEPRECATION_MESSAGE = "[types removal] " + "Specifying types in multi term vector requests is deprecated."; + + @Override + public List routes() { + return List.of( + new Route(GET, "/_mtermvectors"), + new Route(POST, "/_mtermvectors"), + new Route(GET, "/{index}/_mtermvectors"), + new Route(POST, "/{index}/_mtermvectors"), + new Route(GET, "/{index}/{type}/_mtermvectors"), + new Route(POST, "/{index}/{type}/_mtermvectors") + ); + } + + @Override + public String getName() { + return super.getName() + "_v7"; + } + + @Override + public Version compatibleWithVersion() { + return Version.V_7_0_0; + } + + @Override + public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { + TypeConsumer typeConsumer = new TypeConsumer(request, "_type"); + + MultiTermVectorsRequest multiTermVectorsRequest = new MultiTermVectorsRequest(); + TermVectorsRequest template = new TermVectorsRequest().index(request.param("index")); + + RestTermVectorsAction.readURIParameters(template, request); + multiTermVectorsRequest.ids(Strings.commaDelimitedListToStringArray(request.param("ids"))); + request.withContentOrSourceParamParserOrNull(p -> multiTermVectorsRequest.add(template, p, typeConsumer)); + + if (typeConsumer.hasTypes()) { + request.param("type"); + deprecationLogger.deprecate("termvectors_with_types", TYPES_DEPRECATION_MESSAGE); + } + return channel -> client.multiTermVectors(multiTermVectorsRequest, new RestToXContentListener<>(channel)); + } +} diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestTermVectorsActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestTermVectorsActionV7.java new file mode 100644 index 0000000000000..3d083e066432a --- /dev/null +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestTermVectorsActionV7.java @@ -0,0 +1,87 @@ +/* + * 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.rest.action.document; + +import org.elasticsearch.Version; +import org.elasticsearch.action.termvectors.TermVectorsRequest; +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.logging.DeprecationLogger; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.compat.TypeConsumer; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.action.RestToXContentListener; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.rest.RestRequest.Method.GET; +import static org.elasticsearch.rest.RestRequest.Method.POST; + +public class RestTermVectorsActionV7 extends RestTermVectorsAction { + private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(RestTermVectorsActionV7.class); + public static final String TYPES_DEPRECATION_MESSAGE = "[types removal] " + "Specifying types in term vector requests is deprecated."; + + @Override + public List routes() { + return List.of( + new Route(GET, "/{index}/_termvectors"), + new Route(POST, "/{index}/_termvectors"), + new Route(GET, "/{index}/_termvectors/{id}"), + new Route(POST, "/{index}/_termvectors/{id}"), + // Deprecated typed endpoints. + new Route(GET, "/{index}/{type}/_termvectors"), + new Route(POST, "/{index}/{type}/_termvectors"), + new Route(GET, "/{index}/{type}/{id}/_termvectors"), + new Route(POST, "/{index}/{type}/{id}/_termvectors") + ); + } + + @Override + public String getName() { + return super.getName() + "_v7"; + } + + @Override + public Version compatibleWithVersion() { + return Version.V_7_0_0; + } + + @Override + public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { + TypeConsumer typeConsumer = new TypeConsumer(request, "_type"); + + TermVectorsRequest termVectorsRequest = new TermVectorsRequest(request.param("index"), request.param("id")); + + if (request.hasContentOrSourceParam()) { + try (XContentParser parser = request.contentOrSourceParamParser()) { + TermVectorsRequest.parseRequest(termVectorsRequest, parser, typeConsumer); + } + } + readURIParameters(termVectorsRequest, request); + + if (typeConsumer.hasTypes()) { + request.param("type"); + deprecationLogger.deprecate("termvectors_with_types", TYPES_DEPRECATION_MESSAGE); + } + + return channel -> client.termVectors(termVectorsRequest, new RestToXContentListener<>(channel)); + } + +} diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestUpdateActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestUpdateActionV7.java new file mode 100644 index 0000000000000..8881cb744030b --- /dev/null +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestUpdateActionV7.java @@ -0,0 +1,62 @@ +/* + * 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.rest.action.document; + +import org.elasticsearch.Version; +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.logging.DeprecationLogger; +import org.elasticsearch.rest.RestRequest; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.rest.RestRequest.Method.POST; + +public class RestUpdateActionV7 extends RestUpdateAction { + private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(RestGetActionV7.class); + public static final String TYPES_DEPRECATION_MESSAGE = "[types removal] Specifying types in " + + "document update requests is deprecated, use the endpoint /{index}/_update/{id} instead."; + + @Override + public List routes() { + return List.of(new Route(POST, "/{index}/{type}/{id}/_update")); + } + + @Override + public String getName() { + return "document_update_action_v7"; + } + + @Override + public Version compatibleWithVersion() { + return Version.V_7_0_0; + } + + @Override + public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { + if (request.hasParam("type")) { + request.param("type"); + deprecationLogger.deprecate("update_with_types", TYPES_DEPRECATION_MESSAGE); + // todo compatible log + } + + return super.prepareRequest(request, client); + } +} diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/search/RestMultiSearchActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/search/RestMultiSearchActionV7.java new file mode 100644 index 0000000000000..e440925dc2250 --- /dev/null +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/search/RestMultiSearchActionV7.java @@ -0,0 +1,80 @@ +/* + * 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.rest.action.search; + +import org.elasticsearch.Version; +import org.elasticsearch.action.search.MultiSearchRequest; +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.logging.DeprecationLogger; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.compat.TypeConsumer; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.action.RestToXContentListener; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.rest.RestRequest.Method.GET; +import static org.elasticsearch.rest.RestRequest.Method.POST; + +public class RestMultiSearchActionV7 extends RestMultiSearchAction { + private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(RestMultiSearchActionV7.class); + static final String TYPES_DEPRECATION_MESSAGE = "[types removal]" + " Specifying types in multi search requests is deprecated."; + + public RestMultiSearchActionV7(Settings settings) { + super(settings); + } + + @Override + public List routes() { + return List.of( + new Route(GET, "/_msearch"), + new Route(POST, "/_msearch"), + new Route(GET, "/{index}/_msearch"), + new Route(POST, "/{index}/_msearch"), + // Deprecated typed endpoints. + new Route(GET, "/{index}/{type}/_msearch"), + new Route(POST, "/{index}/{type}/_msearch") + ); + } + + @Override + public String getName() { + return super.getName() + "_v7"; + } + + @Override + public Version compatibleWithVersion() { + return Version.V_7_0_0; + } + + @Override + public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { + request.param("type"); + TypeConsumer typeConsumer = new TypeConsumer(request, "type", "types"); + + MultiSearchRequest multiSearchRequest = parseRequest(request, allowExplicitIndex, typeConsumer); + if (typeConsumer.hasTypes()) { + deprecationLogger.deprecate("msearch_with_types", TYPES_DEPRECATION_MESSAGE); + } + return channel -> client.multiSearch(multiSearchRequest, new RestToXContentListener<>(channel)); + } + +} diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/search/RestSearchActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/search/RestSearchActionV7.java new file mode 100644 index 0000000000000..7d64c854d9e36 --- /dev/null +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/search/RestSearchActionV7.java @@ -0,0 +1,71 @@ +/* + * 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.rest.action.search; + +import org.elasticsearch.Version; +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.logging.DeprecationLogger; +import org.elasticsearch.rest.RestRequest; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.rest.RestRequest.Method.GET; +import static org.elasticsearch.rest.RestRequest.Method.POST; + +public class RestSearchActionV7 extends RestSearchAction { + public static final String INCLUDE_TYPE_NAME_PARAMETER = "include_type_name"; + public static final String TYPES_DEPRECATION_MESSAGE = "[types removal]" + " Specifying types in search requests is deprecated."; + + private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(RestSearchActionV7.class); + + @Override + public List routes() { + return List.of( + new Route(GET, "/_search"), + new Route(POST, "/_search"), + new Route(GET, "/{index}/_search"), + new Route(POST, "/{index}/_search"), + new Route(GET, "/{index}/{type}/_search"), + new Route(POST, "/{index}/{type}/_search") + ); + } + + @Override + public String getName() { + return super.getName() + "_v7"; + } + + @Override + public Version compatibleWithVersion() { + return Version.V_7_0_0; + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, final NodeClient client) throws IOException { + request.param(INCLUDE_TYPE_NAME_PARAMETER); + + if (request.hasParam("type")) { + deprecationLogger.deprecate("search_with_types", TYPES_DEPRECATION_MESSAGE); + } + + return super.prepareRequest(request, client); + } +} diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateActionV7.java new file mode 100644 index 0000000000000..87c65cd2d4e59 --- /dev/null +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateActionV7.java @@ -0,0 +1,111 @@ +/* + * 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.script.mustache; + +import org.elasticsearch.compat.TypeConsumer; +import org.elasticsearch.Version; +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.logging.DeprecationLogger; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.action.RestToXContentListener; +import org.elasticsearch.rest.action.search.RestMultiSearchAction; +import org.elasticsearch.rest.action.search.RestSearchAction; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.rest.RestRequest.Method.GET; +import static org.elasticsearch.rest.RestRequest.Method.POST; + +public class RestMultiSearchTemplateActionV7 extends RestMultiSearchTemplateAction { + private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(RestMultiSearchTemplateActionV7.class); + + static final String TYPES_DEPRECATION_MESSAGE = "[types removal]" + + " Specifying types in multi search template requests is deprecated."; + + public RestMultiSearchTemplateActionV7(Settings settings) { + super(settings); + } + + @Override + public List routes() { + return List.of( + new Route(GET, "/_msearch/template"), + new Route(POST, "/_msearch/template"), + new Route(GET, "/{index}/_msearch/template"), + new Route(POST, "/{index}/_msearch/template"), + + // Deprecated typed endpoints. + new Route(GET, "/{index}/{type}/_msearch/template"), + new Route(POST, "/{index}/{type}/_msearch/template") + ); + } + + @Override + public String getName() { + return super.getName() + "_v7"; + } + + @Override + public Version compatibleWithVersion() { + return Version.V_7_0_0; + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + request.param("type"); + + TypeConsumer typeConsumer = new TypeConsumer(request, "type", "types"); + MultiSearchTemplateRequest multiRequest = parseRequest(request, allowExplicitIndex, typeConsumer); + if (typeConsumer.hasTypes()) { + deprecationLogger.deprecate("msearch_with_types", TYPES_DEPRECATION_MESSAGE); + } + return channel -> client.execute(MultiSearchTemplateAction.INSTANCE, multiRequest, new RestToXContentListener<>(channel)); + } + + /** + * Parses a {@link RestRequest} body and returns a {@link MultiSearchTemplateRequest} + */ + public static MultiSearchTemplateRequest parseRequest(RestRequest restRequest, boolean allowExplicitIndex) throws IOException { + MultiSearchTemplateRequest multiRequest = new MultiSearchTemplateRequest(); + if (restRequest.hasParam("max_concurrent_searches")) { + multiRequest.maxConcurrentSearchRequests(restRequest.paramAsInt("max_concurrent_searches", 0)); + } + + RestMultiSearchAction.parseMultiLineRequest( + restRequest, + multiRequest.indicesOptions(), + allowExplicitIndex, + (searchRequest, bytes) -> { + SearchTemplateRequest searchTemplateRequest = SearchTemplateRequest.fromXContent(bytes); + if (searchTemplateRequest.getScript() != null) { + searchTemplateRequest.setRequest(searchRequest); + multiRequest.add(searchTemplateRequest); + } else { + throw new IllegalArgumentException("Malformed search template"); + } + RestSearchAction.checkRestTotalHits(restRequest, searchRequest); + }, + k -> false + ); + return multiRequest; + } +} diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/script/mustache/RestSearchTemplateActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/script/mustache/RestSearchTemplateActionV7.java new file mode 100644 index 0000000000000..8eefa2708d447 --- /dev/null +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/script/mustache/RestSearchTemplateActionV7.java @@ -0,0 +1,68 @@ +/* + * 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.script.mustache; + +import org.elasticsearch.Version; +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.logging.DeprecationLogger; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.action.search.RestSearchActionV7; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.rest.RestRequest.Method.GET; +import static org.elasticsearch.rest.RestRequest.Method.POST; + +public class RestSearchTemplateActionV7 extends RestSearchTemplateAction { + private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(RestSearchTemplateActionV7.class); + + @Override + public List routes() { + return List.of( + new Route(GET, "/_search/template"), + new Route(POST, "/_search/template"), + new Route(GET, "/{index}/_search/template"), + new Route(POST, "/{index}/_search/template"), + // Deprecated typed endpoints. + new Route(GET, "/{index}/{type}/_search/template"), + new Route(POST, "/{index}/{type}/_search/template") + ); + } + + @Override + public String getName() { + return super.getName() + "_v7"; + } + + @Override + public Version compatibleWithVersion() { + return Version.V_7_0_0; + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + if (request.hasParam("type")) { + deprecationLogger.deprecate("search_with_types", RestSearchActionV7.TYPES_DEPRECATION_MESSAGE); + request.param("type"); + } + return super.prepareRequest(request, client); + } +} diff --git a/modules/rest-compatibility/src/test/java/org/elasticsearch/action/DocWriteResponseV7Tests.java b/modules/rest-compatibility/src/test/java/org/elasticsearch/action/DocWriteResponseV7Tests.java new file mode 100644 index 0000000000000..5815f08f6ee2f --- /dev/null +++ b/modules/rest-compatibility/src/test/java/org/elasticsearch/action/DocWriteResponseV7Tests.java @@ -0,0 +1,69 @@ +/* + * 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; + +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.json.JsonXContent; +import org.elasticsearch.index.mapper.MapperService; +import org.elasticsearch.index.seqno.SequenceNumbers; +import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.test.ESTestCase; + +import java.io.IOException; + +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.not; + +public class DocWriteResponseV7Tests extends ESTestCase { + + public void testTypeWhenCompatible() throws IOException { + DocWriteResponse response = new DocWriteResponse( + new ShardId("index", "uuid", 0), + "id", + SequenceNumbers.UNASSIGNED_SEQ_NO, + 17, + 0, + DocWriteResponse.Result.CREATED + ) { + // DocWriteResponse is abstract so we have to sneak a subclass in here to test it. + }; + try (XContentBuilder builder = JsonXContent.contentBuilder()) { + builder.setCompatibleMajorVersion((byte) 7); + response.toXContent(builder, ToXContent.EMPTY_PARAMS); + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, BytesReference.bytes(builder))) { + assertThat(parser.map(), hasEntry(DocWriteResponse.TYPE_FIELD_NAME, MapperService.SINGLE_MAPPING_NAME)); + } + } + + try (XContentBuilder builder = JsonXContent.contentBuilder()) { + builder.setCompatibleMajorVersion((byte) 6); + response.toXContent(builder, ToXContent.EMPTY_PARAMS); + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, BytesReference.bytes(builder))) { + assertThat(parser.map(), not(hasKey(DocWriteResponse.TYPE_FIELD_NAME))); + } + } + } +} diff --git a/modules/rest-compatibility/src/test/java/org/elasticsearch/compat/FakeCompatRestRequestBuilder.java b/modules/rest-compatibility/src/test/java/org/elasticsearch/compat/FakeCompatRestRequestBuilder.java new file mode 100644 index 0000000000000..9c2d98ad2f4f0 --- /dev/null +++ b/modules/rest-compatibility/src/test/java/org/elasticsearch/compat/FakeCompatRestRequestBuilder.java @@ -0,0 +1,44 @@ +/* + * 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.compat; + +import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.test.rest.FakeRestRequest; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static org.elasticsearch.test.ESTestCase.randomFrom; + +public class FakeCompatRestRequestBuilder extends FakeRestRequest.Builder { + final String mimeType = randomFrom("application/vnd.elasticsearch+json;compatible-with=7"); + final List contentTypeHeader = Collections.singletonList(mimeType); + + public FakeCompatRestRequestBuilder(NamedXContentRegistry xContentRegistry) { + super(xContentRegistry); + } + + @Override + public FakeRestRequest build() { + addHeaders(Map.of("Content-Type", contentTypeHeader, "Accept", contentTypeHeader)); + return super.build(); + } +} diff --git a/modules/rest-compatibility/src/test/java/org/elasticsearch/compat/RestCompatPluginTests.java b/modules/rest-compatibility/src/test/java/org/elasticsearch/compat/RestCompatPluginTests.java new file mode 100644 index 0000000000000..212ab690ad0b7 --- /dev/null +++ b/modules/rest-compatibility/src/test/java/org/elasticsearch/compat/RestCompatPluginTests.java @@ -0,0 +1,59 @@ +/* + * 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.compat; + +import org.elasticsearch.Version; +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.rest.RestChannel; +import org.elasticsearch.rest.RestHandler; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.test.ESTestCase; +import org.hamcrest.Matchers; + +import java.util.List; + +public class RestCompatPluginTests extends ESTestCase { + + public void testRestHandlersValidation() { + RestCompatPlugin restCompatPlugin = new RestCompatPlugin(); + Version prevVersion = Version.fromString("7.0.0"); + expectThrows( + IllegalStateException.class, + () -> restCompatPlugin.validateCompatibleHandlers(7, restHandler(prevVersion), restHandler(Version.fromString("8.0.0"))) + ); + + List restHandlers = restCompatPlugin.validateCompatibleHandlers(7, restHandler(prevVersion), restHandler(prevVersion)); + assertThat(restHandlers, Matchers.hasSize(2)); + } + + private RestHandler restHandler(final Version version) { + return new RestHandler() { + @Override + public Version compatibleWithVersion() { + return version; + } + + @Override + public void handleRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception { + + } + }; + } +} diff --git a/modules/rest-compatibility/src/test/java/org/elasticsearch/index/reindex/RestDeleteByQueryActionV7Tests.java b/modules/rest-compatibility/src/test/java/org/elasticsearch/index/reindex/RestDeleteByQueryActionV7Tests.java new file mode 100644 index 0000000000000..7722a1a9dcc97 --- /dev/null +++ b/modules/rest-compatibility/src/test/java/org/elasticsearch/index/reindex/RestDeleteByQueryActionV7Tests.java @@ -0,0 +1,65 @@ +/* + * 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.index.reindex; + +import org.elasticsearch.compat.FakeCompatRestRequestBuilder; +import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.action.search.RestSearchActionV7; +import org.elasticsearch.test.rest.RestActionTestCase; +import org.junit.Before; + +import java.io.IOException; + +import static java.util.Collections.emptyList; + +public class RestDeleteByQueryActionV7Tests extends RestActionTestCase { + private RestDeleteByQueryActionV7 action; + + @Before + public void setUpAction() { + action = new RestDeleteByQueryActionV7(); + controller().registerHandler(action); + } + + public void testTypeInPath() throws IOException { + RestRequest request = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(RestRequest.Method.POST) + .withPath("/some_index/some_type/_delete_by_query") + .build(); + dispatchRequest(request); + + // checks the type in the URL is propagated correctly to the request object + // only works after the request is dispatched, so its params are filled from url. + DeleteByQueryRequest dbqRequest = action.buildRequest(request); + // assertArrayEquals(new String[]{"some_type"}, dbqRequest.getDocTypes()); + + // RestDeleteByQueryAction itself doesn't check for a deprecated type usage + // checking here for a deprecation from its internal search request + assertWarnings(RestSearchActionV7.TYPES_DEPRECATION_MESSAGE); + } + + public void testParseEmpty() throws IOException { + DeleteByQueryRequest request = action.buildRequest( + new FakeCompatRestRequestBuilder(new NamedXContentRegistry(emptyList())).build() + ); + // assertEquals(AbstractBulkByScrollRequest.SIZE_ALL_MATCHES, request.getSize()); + assertEquals(AbstractBulkByScrollRequest.DEFAULT_SCROLL_SIZE, request.getSearchRequest().source().size()); + } +} diff --git a/modules/rest-compatibility/src/test/java/org/elasticsearch/index/reindex/RestUpdateByQueryActionTests.java b/modules/rest-compatibility/src/test/java/org/elasticsearch/index/reindex/RestUpdateByQueryActionTests.java new file mode 100644 index 0000000000000..4f97bbdaa9585 --- /dev/null +++ b/modules/rest-compatibility/src/test/java/org/elasticsearch/index/reindex/RestUpdateByQueryActionTests.java @@ -0,0 +1,66 @@ +/* + * 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.index.reindex; + +import org.elasticsearch.compat.FakeCompatRestRequestBuilder; +import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.action.search.RestSearchActionV7; +import org.elasticsearch.test.rest.RestActionTestCase; +import org.junit.Before; + +import java.io.IOException; + +import static java.util.Collections.emptyList; + +public class RestUpdateByQueryActionTests extends RestActionTestCase { + + private RestUpdateByQueryAction action; + + @Before + public void setUpAction() { + action = new RestUpdateByQueryActionV7(); + controller().registerHandler(action); + } + + public void testTypeInPath() throws IOException { + RestRequest request = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(RestRequest.Method.POST) + .withPath("/some_index/some_type/_update_by_query") + .build(); + dispatchRequest(request); + + // checks the type in the URL is propagated correctly to the request object + // only works after the request is dispatched, so its params are filled from url. + UpdateByQueryRequest ubqRequest = action.buildRequest(request); + // assertArrayEquals(new String[]{"some_type"}, ubqRequest.getDocTypes()); + + // RestUpdateByQueryAction itself doesn't check for a deprecated type usage + // checking here for a deprecation from its internal search request + assertWarnings(RestSearchActionV7.TYPES_DEPRECATION_MESSAGE); + } + + public void testParseEmpty() throws IOException { + UpdateByQueryRequest request = action.buildRequest( + new FakeCompatRestRequestBuilder(new NamedXContentRegistry(emptyList())).build() + ); + // assertEquals(AbstractBulkByScrollRequest.SIZE_ALL_MATCHES, request.getSize()); + assertEquals(AbstractBulkByScrollRequest.DEFAULT_SCROLL_SIZE, request.getSearchRequest().source().size()); + } +} diff --git a/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/admin/indices/RestCreateIndexActionV7Tests.java b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/admin/indices/RestCreateIndexActionV7Tests.java new file mode 100644 index 0000000000000..35140a22b1479 --- /dev/null +++ b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/admin/indices/RestCreateIndexActionV7Tests.java @@ -0,0 +1,69 @@ +/* + * 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.rest.action.admin.indices; + +import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.compat.FakeCompatRestRequestBuilder; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.test.rest.RestActionTestCase; +import org.junit.Before; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.Matchers.equalTo; + +public class RestCreateIndexActionV7Tests extends RestActionTestCase { + + RestCreateIndexActionV7 restHandler = new RestCreateIndexActionV7(); + + @Before + public void setUpAction() { + controller().registerHandler(restHandler); + } + + public void testTypeInMapping() throws IOException { + String content = "{\n" + + " \"mappings\": {\n" + + " \"some_type\": {\n" + + " \"properties\": {\n" + + " \"field1\": {\n" + + " \"type\": \"text\"\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + "}"; + + Map params = new HashMap<>(); + params.put(RestCreateIndexActionV7.INCLUDE_TYPE_NAME_PARAMETER, "true"); + RestRequest request = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(RestRequest.Method.PUT) + .withPath("/some_index") + .withParams(params) + .withContent(new BytesArray(content), null) + .build(); + + CreateIndexRequest createIndexRequest = restHandler.prepareV7Request(request); + // some_type is replaced with _doc + assertThat(createIndexRequest.mappings(), equalTo("{\"_doc\":{\"properties\":{\"field1\":{\"type\":\"text\"}}}}")); + } +} diff --git a/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestDeleteActionV7Tests.java b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestDeleteActionV7Tests.java new file mode 100644 index 0000000000000..4f0cf4ab318cb --- /dev/null +++ b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestDeleteActionV7Tests.java @@ -0,0 +1,46 @@ +/* + * 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.rest.action.document; + +import org.elasticsearch.compat.FakeCompatRestRequestBuilder; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.test.rest.RestActionTestCase; +import org.junit.Before; + +public class RestDeleteActionV7Tests extends RestActionTestCase { + @Before + public void setUpAction() { + controller().registerHandler(new RestDeleteActionV7()); + controller().registerHandler(new RestDeleteAction()); + } + + public void testTypeInPath() { + RestRequest deprecatedRequest = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(RestRequest.Method.DELETE) + .withPath("/some_index/some_type/some_id") + .build(); + dispatchRequest(deprecatedRequest); + assertWarnings(RestDeleteActionV7.TYPES_DEPRECATION_MESSAGE); + + RestRequest validRequest = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(RestRequest.Method.DELETE) + .withPath("/some_index/_doc/some_id") + .build(); + dispatchRequest(validRequest); + } +} diff --git a/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestGetActionV7Tests.java b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestGetActionV7Tests.java new file mode 100644 index 0000000000000..725e414fae95a --- /dev/null +++ b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestGetActionV7Tests.java @@ -0,0 +1,51 @@ +/* + * 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.rest.action.document; + +import org.elasticsearch.compat.FakeCompatRestRequestBuilder; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.test.rest.FakeRestRequest; +import org.elasticsearch.test.rest.RestActionTestCase; +import org.junit.Before; + +public class RestGetActionV7Tests extends RestActionTestCase { + + @Before + public void setUpAction() { + controller().registerHandler(new RestGetActionV7()); + } + + public void testTypeInPathWithGet() { + FakeRestRequest deprecatedRequest = new FakeCompatRestRequestBuilder(xContentRegistry()).withPath("/some_index/some_type/some_id") + .withMethod(RestRequest.Method.GET) + .build(); + dispatchRequest(deprecatedRequest); + assertWarnings(RestGetActionV7.TYPES_DEPRECATION_MESSAGE); + } + + public void testTypeInPathWithHead() { + FakeRestRequest deprecatedRequest = new FakeCompatRestRequestBuilder(xContentRegistry()).withPath("/some_index/some_type/some_id") + .withMethod(RestRequest.Method.HEAD) + .build(); + dispatchRequest(deprecatedRequest); + assertWarnings(RestGetActionV7.TYPES_DEPRECATION_MESSAGE); + } + +} diff --git a/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestIndexActionV7Tests.java b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestIndexActionV7Tests.java new file mode 100644 index 0000000000000..69812d67333a0 --- /dev/null +++ b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestIndexActionV7Tests.java @@ -0,0 +1,72 @@ +/* + * 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.rest.action.document; + +import org.elasticsearch.compat.FakeCompatRestRequestBuilder; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.test.rest.RestActionTestCase; +import org.junit.Before; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +public class RestIndexActionV7Tests extends RestActionTestCase { + + final String mimeType = "application/vnd.elasticsearch+json;compatible-with=7"; + final List contentTypeHeader = Collections.singletonList(mimeType); + + private final AtomicReference clusterStateSupplier = new AtomicReference<>(); + + @Before + public void setUpAction() { + controller().registerHandler(new RestIndexActionV7.CompatibleRestIndexAction()); + controller().registerHandler(new RestIndexActionV7.CompatibleCreateHandler()); + controller().registerHandler(new RestIndexActionV7.CompatibleAutoIdHandler(() -> clusterStateSupplier.get().nodes())); + } + + public void testTypeInPath() { + // using CompatibleRestIndexAction + RestRequest deprecatedRequest = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(RestRequest.Method.PUT) + .withPath("/some_index/some_type/some_id") + .build(); + dispatchRequest(deprecatedRequest); + assertWarnings(RestIndexActionV7.TYPES_DEPRECATION_MESSAGE); + } + + public void testCreateWithTypeInPath() { + // using CompatibleCreateHandler + RestRequest deprecatedRequest = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(RestRequest.Method.PUT) + .withPath("/some_index/some_type/some_id/_create") + .build(); + dispatchRequest(deprecatedRequest); + assertWarnings(RestIndexActionV7.TYPES_DEPRECATION_MESSAGE); + } + + public void testAutoIdWithType() { + // using CompatibleAutoIdHandler + RestRequest deprecatedRequest = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(RestRequest.Method.POST) + .withPath("/some_index/some_type/") + .build(); + dispatchRequest(deprecatedRequest); + assertWarnings(RestIndexActionV7.TYPES_DEPRECATION_MESSAGE); + } +} diff --git a/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestMultiTermVectorsActionV7Tests.java b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestMultiTermVectorsActionV7Tests.java new file mode 100644 index 0000000000000..67623ebff8e0e --- /dev/null +++ b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestMultiTermVectorsActionV7Tests.java @@ -0,0 +1,86 @@ +/* + * 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.rest.action.document; + +import org.elasticsearch.compat.FakeCompatRestRequestBuilder; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.RestRequest.Method; +import org.elasticsearch.test.rest.RestActionTestCase; +import org.junit.Before; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class RestMultiTermVectorsActionV7Tests extends RestActionTestCase { + + @Before + public void setUpAction() { + controller().registerHandler(new RestMultiTermVectorsActionV7()); + } + + public void testTypeInPath() { + RestRequest request = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(Method.POST) + .withPath("/some_index/some_type/_mtermvectors") + .build(); + + dispatchRequest(request); + assertWarnings(RestMultiTermVectorsActionV7.TYPES_DEPRECATION_MESSAGE); + } + + public void testTypeParameter() { + Map params = new HashMap<>(); + params.put("type", "some_type"); + + RestRequest request = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(Method.GET) + .withPath("/some_index/_mtermvectors") + .withParams(params) + .build(); + + dispatchRequest(request); + assertWarnings(RestMultiTermVectorsActionV7.TYPES_DEPRECATION_MESSAGE); + } + + public void testTypeInBody() throws IOException { + XContentBuilder content = XContentFactory.jsonBuilder() + .startObject() + .startArray("docs") + .startObject() + .field("_type", "some_type") + .field("_id", 1) + .endObject() + .endArray() + .endObject(); + + RestRequest request = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(Method.GET) + .withPath("/some_index/_mtermvectors") + .withContent(BytesReference.bytes(content), XContentType.JSON) + .build(); + + dispatchRequest(request); + // TODO change - now the deprecation warning is from MultiTermVectors.. + // assertWarnings(RestTermVectorsActionV7.TYPES_DEPRECATION_MESSAGE); + assertWarnings(RestMultiTermVectorsActionV7.TYPES_DEPRECATION_MESSAGE); + } +} diff --git a/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestTermVectorsActionV7Tests.java b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestTermVectorsActionV7Tests.java new file mode 100644 index 0000000000000..3aa7503e74813 --- /dev/null +++ b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestTermVectorsActionV7Tests.java @@ -0,0 +1,61 @@ +/* + * 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.rest.action.document; + +import org.elasticsearch.compat.FakeCompatRestRequestBuilder; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.RestRequest.Method; +import org.elasticsearch.test.rest.RestActionTestCase; +import org.junit.Before; + +import java.io.IOException; + +public class RestTermVectorsActionV7Tests extends RestActionTestCase { + + @Before + public void setUpAction() { + controller().registerHandler(new RestTermVectorsActionV7()); + } + + public void testTypeInPath() { + RestRequest request = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(Method.POST) + .withPath("/some_index/some_type/some_id/_termvectors") + .build(); + + dispatchRequest(request); + assertWarnings(RestTermVectorsActionV7.TYPES_DEPRECATION_MESSAGE); + } + + public void testTypeInBody() throws IOException { + XContentBuilder content = XContentFactory.jsonBuilder().startObject().field("_type", "some_type").field("_id", 1).endObject(); + + RestRequest request = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(Method.GET) + .withPath("/some_index/_termvectors/some_id") + .withContent(BytesReference.bytes(content), XContentType.JSON) + .build(); + + dispatchRequest(request); + assertWarnings(RestTermVectorsActionV7.TYPES_DEPRECATION_MESSAGE); + } +} diff --git a/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestUpdateActionV7Tests.java b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestUpdateActionV7Tests.java new file mode 100644 index 0000000000000..cf659140af9b9 --- /dev/null +++ b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestUpdateActionV7Tests.java @@ -0,0 +1,46 @@ +/* + * 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.rest.action.document; + +import org.elasticsearch.compat.FakeCompatRestRequestBuilder; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.test.rest.RestActionTestCase; +import org.junit.Before; + +public class RestUpdateActionV7Tests extends RestActionTestCase { + @Before + public void setUpAction() { + controller().registerHandler(new RestUpdateActionV7()); + controller().registerHandler(new RestUpdateAction()); + } + + public void testTypeInPath() { + RestRequest deprecatedRequest = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(RestRequest.Method.POST) + .withPath("/some_index/some_type/some_id/_update") + .build(); + dispatchRequest(deprecatedRequest); + assertWarnings(RestUpdateActionV7.TYPES_DEPRECATION_MESSAGE); + + RestRequest validRequest = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(RestRequest.Method.POST) + .withPath("/some_index/_update/some_id") + .build(); + dispatchRequest(validRequest); + } +} diff --git a/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/search/RestMultiSearchActionV7Tests.java b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/search/RestMultiSearchActionV7Tests.java new file mode 100644 index 0000000000000..b221e9ebf6295 --- /dev/null +++ b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/search/RestMultiSearchActionV7Tests.java @@ -0,0 +1,64 @@ +/* + * 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.rest.action.search; + +import org.elasticsearch.compat.FakeCompatRestRequestBuilder; +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.test.rest.RestActionTestCase; +import org.junit.Before; + +import java.nio.charset.StandardCharsets; + +public class RestMultiSearchActionV7Tests extends RestActionTestCase { + + @Before + public void setUpAction() { + controller().registerHandler(new RestMultiSearchActionV7(Settings.EMPTY)); + } + + public void testTypeInPath() { + String content = "{ \"index\": \"some_index\" } \n {} \n"; + BytesArray bytesContent = new BytesArray(content.getBytes(StandardCharsets.UTF_8)); + + RestRequest request = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(RestRequest.Method.GET) + .withPath("/some_index/some_type/_msearch") + .withContent(bytesContent, XContentType.JSON) + .build(); + + dispatchRequest(request); + assertWarnings(RestMultiSearchActionV7.TYPES_DEPRECATION_MESSAGE); + } + + public void testTypeInBody() { + String content = "{ \"index\": \"some_index\", \"type\": \"some_type\" } \n {} \n"; + BytesArray bytesContent = new BytesArray(content.getBytes(StandardCharsets.UTF_8)); + + RestRequest request = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(RestRequest.Method.POST) + .withPath("/some_index/_msearch") + .withContent(bytesContent, XContentType.JSON) + .build(); + + dispatchRequest(request); + assertWarnings(RestMultiSearchActionV7.TYPES_DEPRECATION_MESSAGE); + } +} diff --git a/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/search/RestSearchActionV7Tests.java b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/search/RestSearchActionV7Tests.java new file mode 100644 index 0000000000000..66396725ec63c --- /dev/null +++ b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/search/RestSearchActionV7Tests.java @@ -0,0 +1,58 @@ +/* + * 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.rest.action.search; + +import org.elasticsearch.compat.FakeCompatRestRequestBuilder; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.test.rest.RestActionTestCase; +import org.junit.Before; + +import java.util.HashMap; +import java.util.Map; + +public class RestSearchActionV7Tests extends RestActionTestCase { + + @Before + public void setUpAction() { + controller().registerHandler(new RestSearchActionV7()); + } + + public void testTypeInPath() { + RestRequest request = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(RestRequest.Method.GET) + .withPath("/some_index/some_type/_search") + .build(); + + dispatchRequest(request); + assertWarnings(RestSearchActionV7.TYPES_DEPRECATION_MESSAGE); + } + + public void testTypeParameter() { + Map params = new HashMap<>(); + params.put("type", "some_type"); + + RestRequest request = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(RestRequest.Method.GET) + .withPath("/some_index/_search") + .withParams(params) + .build(); + + dispatchRequest(request); + assertWarnings(RestSearchActionV7.TYPES_DEPRECATION_MESSAGE); + } +} diff --git a/modules/rest-compatibility/src/test/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateActionV7Tests.java b/modules/rest-compatibility/src/test/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateActionV7Tests.java new file mode 100644 index 0000000000000..4b3439b2514b6 --- /dev/null +++ b/modules/rest-compatibility/src/test/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateActionV7Tests.java @@ -0,0 +1,62 @@ +/* + * 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.script.mustache; + +import org.elasticsearch.compat.FakeCompatRestRequestBuilder; +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.test.rest.RestActionTestCase; +import org.junit.Before; + +import java.nio.charset.StandardCharsets; + +public class RestMultiSearchTemplateActionV7Tests extends RestActionTestCase { + + @Before + public void setUpAction() { + controller().registerHandler(new RestMultiSearchTemplateActionV7(Settings.EMPTY)); + } + + public void testTypeInPath() { + String content = "{ \"index\": \"some_index\" } \n" + "{\"source\": {\"query\" : {\"match_all\" :{}}}} \n"; + BytesArray bytesContent = new BytesArray(content.getBytes(StandardCharsets.UTF_8)); + + RestRequest request = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(RestRequest.Method.GET) + .withPath("/some_index/some_type/_msearch/template") + .withContent(bytesContent, XContentType.JSON) + .build(); + + dispatchRequest(request); + assertWarnings(RestMultiSearchTemplateActionV7.TYPES_DEPRECATION_MESSAGE); + } + + public void testTypeInBody() { + String content = "{ \"index\": \"some_index\", \"type\": \"some_type\" } \n" + "{\"source\": {\"query\" : {\"match_all\" :{}}}} \n"; + BytesArray bytesContent = new BytesArray(content.getBytes(StandardCharsets.UTF_8)); + + RestRequest request = new FakeCompatRestRequestBuilder(xContentRegistry()).withPath("/some_index/_msearch/template") + .withContent(bytesContent, XContentType.JSON) + .build(); + + dispatchRequest(request); + assertWarnings(RestMultiSearchTemplateActionV7.TYPES_DEPRECATION_MESSAGE); + } +} diff --git a/modules/rest-compatibility/src/test/java/org/elasticsearch/script/mustache/RestSearchTemplateActionV7Tests.java b/modules/rest-compatibility/src/test/java/org/elasticsearch/script/mustache/RestSearchTemplateActionV7Tests.java new file mode 100644 index 0000000000000..f22e159380be5 --- /dev/null +++ b/modules/rest-compatibility/src/test/java/org/elasticsearch/script/mustache/RestSearchTemplateActionV7Tests.java @@ -0,0 +1,58 @@ +/* + * 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.script.mustache; + +import org.elasticsearch.compat.FakeCompatRestRequestBuilder; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.action.search.RestSearchActionV7; +import org.elasticsearch.test.rest.RestActionTestCase; +import org.junit.Before; + +import java.util.HashMap; +import java.util.Map; + +public class RestSearchTemplateActionV7Tests extends RestActionTestCase { + + @Before + public void setUpAction() { + controller().registerHandler(new RestSearchTemplateActionV7()); + } + + public void testTypeInPath() { + RestRequest request = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(RestRequest.Method.GET) + .withPath("/some_index/some_type/_search/template") + .build(); + + dispatchRequest(request); + assertWarnings(RestSearchActionV7.TYPES_DEPRECATION_MESSAGE); + } + + public void testTypeParameter() { + Map params = new HashMap<>(); + params.put("type", "some_type"); + + RestRequest request = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(RestRequest.Method.GET) + .withPath("/some_index/_search/template") + .withParams(params) + .build(); + + dispatchRequest(request); + assertWarnings(RestSearchActionV7.TYPES_DEPRECATION_MESSAGE); + } +} diff --git a/modules/transport-netty4/src/test/java/org/elasticsearch/rest/Netty4BadRequestIT.java b/modules/transport-netty4/src/test/java/org/elasticsearch/rest/Netty4BadRequestIT.java index cfda71f10096e..ecf7e86e748ef 100644 --- a/modules/transport-netty4/src/test/java/org/elasticsearch/rest/Netty4BadRequestIT.java +++ b/modules/transport-netty4/src/test/java/org/elasticsearch/rest/Netty4BadRequestIT.java @@ -101,4 +101,27 @@ public void testInvalidHeaderValue() throws IOException { assertThat(map.get("type"), equalTo("content_type_header_exception")); assertThat(map.get("reason"), equalTo("java.lang.IllegalArgumentException: invalid Content-Type header []")); } + + public void testInvalidHeaderCombinations() throws IOException { + final Request request = new Request("GET", "/_cluster/settings"); + final RequestOptions.Builder options = request.getOptions().toBuilder(); + options.addHeader("Content-Type", "application/vnd.elasticsearch+json;compatible-with=7"); + options.addHeader("Accept", "application/vnd.elasticsearch+json;compatible-with=8"); + request.setOptions(options); + request.setJsonEntity("{\"transient\":{\"search.*\":null}}"); + + final ResponseException e = expectThrows(ResponseException.class, () -> client().performRequest(request)); + final Response response = e.getResponse(); + assertThat(response.getStatusLine().getStatusCode(), equalTo(400)); + final ObjectPath objectPath = ObjectPath.createFromResponse(response); + final Map map = objectPath.evaluate("error"); + assertThat(map.get("type"), equalTo("compatible_api_headers_combination_exception")); + assertThat(map.get("reason"), equalTo("Content-Type and Accept headers have to match when content is present. " + + "Accept=application/vnd.elasticsearch+json;compatible-with=8 " + + "Content-Type=application/vnd.elasticsearch+json;compatible-with=7 " + + "hasContent=true " + + "path=/_cluster/settings " + + "params={} " + + "method=GET")); + } } diff --git a/qa/rest-compat-tests/build.gradle b/qa/rest-compat-tests/build.gradle new file mode 100644 index 0000000000000..78fac721fa843 --- /dev/null +++ b/qa/rest-compat-tests/build.gradle @@ -0,0 +1,26 @@ +/** + * WARNING : This is a temporary test fixture and will be replaced with a less hacky implementation. + **/ +apply plugin: 'elasticsearch.testclusters' +apply plugin: 'elasticsearch.build' +apply plugin: 'elasticsearch.rest-test' + +// just hard code the paths for now. This is not a long term solution ! +System.out.println("Copying compat REST resources from: " + new File(project(':distribution:bwc:minor').projectDir, 'build/bwc/checkout-7.x/rest-api-spec/src/main/resources/')); +task copyRestTestsResources(type: Copy) { + into project.sourceSets.test.output.resourcesDir + new File(project(':server').projectDir, 'licenses') + from new File(project(':distribution:bwc:minor').projectDir, 'build/bwc/checkout-7.x/rest-api-spec/src/main/resources/') + include 'rest-api-spec/**' + dependsOn ':distribution:bwc:minor:checkoutBwcBranch' +} + +//copyOverrides.dependsOn(copyRestTestsResources) +processTestResources.dependsOn(copyRestTestsResources) +integTest.dependsOn(copyRestTestsResources) + +dependencies { + implementation project(':test:framework') +} + +test.enabled = false diff --git a/qa/rest-compat-tests/src/main/java/org/elasticsearch/rest/compat/AbstractCompatRestTest.java b/qa/rest-compat-tests/src/main/java/org/elasticsearch/rest/compat/AbstractCompatRestTest.java new file mode 100644 index 0000000000000..a770139c44aa3 --- /dev/null +++ b/qa/rest-compat-tests/src/main/java/org/elasticsearch/rest/compat/AbstractCompatRestTest.java @@ -0,0 +1,114 @@ +/* + * 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.rest.compat; + +import com.carrotsearch.randomizedtesting.annotations.Name; +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.Version; +import org.elasticsearch.rest.CompatibleConstants; +import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; +import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase; +import org.elasticsearch.test.rest.yaml.section.DoSection; +import org.elasticsearch.test.rest.yaml.section.ExecutableSection; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.stream.StreamSupport; + +/** + * Warning - temporary implementation. This will be replaced soon. + */ +public class AbstractCompatRestTest extends ESClientYamlSuiteTestCase { + protected AbstractCompatRestTest(@Name("yaml") ClientYamlTestCandidate testCandidate) { + super(testCandidate); + } + + private static final Logger staticLogger = LogManager.getLogger(AbstractCompatRestTest.class); + + public static final String COMPAT_TESTS_PATH = "/rest-api-spec/test-compat"; + + @ParametersFactory + public static Iterable createParameters() throws Exception { + List finalTestCandidates = new ArrayList<>(); + Iterable bwcCandidates = ESClientYamlSuiteTestCase.createParameters(); + Map localCandidates = getLocalCompatibilityTests(); + + for (Object[] candidateArray : bwcCandidates) { + List testCandidates = new ArrayList<>(1); + Arrays.stream(candidateArray).map(o -> (ClientYamlTestCandidate) o).forEach(testCandidate -> { + if (localCandidates.containsKey(testCandidate)) { + staticLogger.info("Overriding test [{}] with local test.", testCandidate.toString()); + testCandidate = localCandidates.remove(testCandidate); + } + mutateTestCandidate(testCandidate); + testCandidates.add(testCandidate); + }); + finalTestCandidates.add(testCandidates.toArray()); + } + localCandidates.keySet().forEach(lc -> finalTestCandidates.add(new Object[] { lc })); + return finalTestCandidates; + } + + private static void mutateTestCandidate(ClientYamlTestCandidate testCandidate) { + testCandidate.getSetupSection().getExecutableSections().stream().filter(s -> s instanceof DoSection).forEach(updateDoSection()); + testCandidate.getTestSection().getExecutableSections().stream().filter(s -> s instanceof DoSection).forEach(updateDoSection()); + } + + private static Consumer updateDoSection() { + return ds -> { + DoSection doSection = (DoSection) ds; + // TODO: be more selective here + doSection.setIgnoreWarnings(true); + + String compatibleHeader = createCompatibleHeader(); + // TODO for cat apis accept headers would break tests which expect txt response + if (doSection.getApiCallSection().getApi().startsWith("cat") == false) { + doSection.getApiCallSection() + .addHeaders( + Map.of( + CompatibleConstants.COMPATIBLE_ACCEPT_HEADER, + compatibleHeader, + CompatibleConstants.COMPATIBLE_CONTENT_TYPE_HEADER, + compatibleHeader + ) + ); + } + + }; + } + + private static String createCompatibleHeader() { + return "application/vnd.elasticsearch+json;compatible-with=" + Version.minimumRestCompatibilityVersion().major; + } + + private static Map getLocalCompatibilityTests() throws Exception { + Iterable candidates = ESClientYamlSuiteTestCase.createParameters(ExecutableSection.XCONTENT_REGISTRY, COMPAT_TESTS_PATH); + Map localCompatibilityTests = new HashMap<>(); + StreamSupport.stream(candidates.spliterator(), false) + .flatMap(Arrays::stream) + .forEach(o -> localCompatibilityTests.put((ClientYamlTestCandidate) o, (ClientYamlTestCandidate) o)); + return localCompatibilityTests; + } +} diff --git a/qa/rest-compat-tests/src/test/java/org/elasticsearch/rest/compat/CompatRestIT.java b/qa/rest-compat-tests/src/test/java/org/elasticsearch/rest/compat/CompatRestIT.java new file mode 100644 index 0000000000000..0aa4201778aa8 --- /dev/null +++ b/qa/rest-compat-tests/src/test/java/org/elasticsearch/rest/compat/CompatRestIT.java @@ -0,0 +1,33 @@ +/* + * 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.rest.compat; + +import com.carrotsearch.randomizedtesting.annotations.Name; +import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; + +/** + * Warning - temporary implementation. This will be replaced soon. + */ +public class CompatRestIT extends AbstractCompatRestTest { + + public CompatRestIT(@Name("yaml") ClientYamlTestCandidate testCandidate) { + super(testCandidate); + } + +} diff --git a/qa/rest-compat-tests/src/test/resources/rest-api-spec/test-compat/testme/10_testme.yml b/qa/rest-compat-tests/src/test/resources/rest-api-spec/test-compat/testme/10_testme.yml new file mode 100644 index 0000000000000..4a2454bb6086c --- /dev/null +++ b/qa/rest-compat-tests/src/test/resources/rest-api-spec/test-compat/testme/10_testme.yml @@ -0,0 +1,13 @@ +--- +"Test Me": # TODO: delete this + + - do: + index: + index: test_1 + id: 1 + body: { foo: bar } + version_type: external + version: 5 + + - match: { _version: 5} + diff --git a/qa/rest-compat-tests/src/test/resources/rest-api-spec/test/indices.get_field_mapping/11_basic_with_types.yml b/qa/rest-compat-tests/src/test/resources/rest-api-spec/test/indices.get_field_mapping/11_basic_with_types.yml new file mode 100644 index 0000000000000..4d62b24184e56 --- /dev/null +++ b/qa/rest-compat-tests/src/test/resources/rest-api-spec/test/indices.get_field_mapping/11_basic_with_types.yml @@ -0,0 +1,85 @@ +--- +setup: + - do: + indices.create: + include_type_name: true + index: test_index + body: + mappings: + test_type: + properties: + text: + type: text + +--- +"Get field mapping with no index and type": + + - do: + indices.get_field_mapping: + include_type_name: true + fields: text + #- match: {test_index.mappings.test_type.text.mapping.text.type: text} + - match: {test_index.mappings._doc.text.mapping.text.type: text} + +--- +"Get field mapping by index only": + - do: + indices.get_field_mapping: + include_type_name: true + index: test_index + fields: text +# - match: {test_index.mappings.test_type.text.mapping.text.type: text} + - match: {test_index.mappings._doc.text.mapping.text.type: text} + +--- +"Get field mapping by type & field": + + - do: + indices.get_field_mapping: + include_type_name: true + index: test_index + type: test_type + fields: text +# - match: {test_index.mappings.test_type.text.mapping.text.type: text} + - match: {test_index.mappings._doc.text.mapping.text.type: text} + +--- +"Get field mapping by type & field, with another field that doesn't exist": + + - do: + indices.get_field_mapping: + include_type_name: true + index: test_index + type: test_type + fields: [ text , text1 ] +#- match: {test_index.mappings.test_type.text.mapping.text.type: text} +# - is_false: test_index.mappings.test_type.text1 + - match: {test_index.mappings._doc.text.mapping.text.type: text} + - is_false: test_index.mappings._doc.text1 + +--- +"Get field mapping with include_defaults": + + - do: + indices.get_field_mapping: + include_type_name: true + index: test_index + type: test_type + fields: text + include_defaults: true +#- match: {test_index.mappings.test_type.text.mapping.text.type: text} +# - match: {test_index.mappings.test_type.text.mapping.text.analyzer: default} + - match: {test_index.mappings._doc.text.mapping.text.type: text} + - match: {test_index.mappings._doc.text.mapping.text.analyzer: default} + +--- +"Get field mapping should work without index specifying type and fields": + + - do: + indices.get_field_mapping: + include_type_name: true + type: test_type + fields: text +# - match: {test_index.mappings.test_type.text.mapping.text.type: text} + - match: {test_index.mappings._doc.text.mapping.text.type: text} + diff --git a/qa/rest-compat-tests/src/test/resources/rest-api-spec/test/indices.get_field_mapping/21_missing_field_with_types.yml b/qa/rest-compat-tests/src/test/resources/rest-api-spec/test/indices.get_field_mapping/21_missing_field_with_types.yml new file mode 100644 index 0000000000000..da8588b1ba7d6 --- /dev/null +++ b/qa/rest-compat-tests/src/test/resources/rest-api-spec/test/indices.get_field_mapping/21_missing_field_with_types.yml @@ -0,0 +1,23 @@ +--- +"Return empty object if field doesn't exist, but type and index do": + + - do: + indices.create: + include_type_name: true + index: test_index + body: + mappings: + test_type: + properties: + text: + type: text + analyzer: whitespace + + - do: + indices.get_field_mapping: + include_type_name: true + index: test_index + type: test_type + fields: not_existent +# - match: { '': {}} + - match: { 'test_index.mappings': { '_doc':{}}} diff --git a/qa/rest-compat-tests/src/test/resources/rest-api-spec/test/indices.get_field_mapping/51_field_wildcards_with_types.yml b/qa/rest-compat-tests/src/test/resources/rest-api-spec/test/indices.get_field_mapping/51_field_wildcards_with_types.yml new file mode 100644 index 0000000000000..f7943439cce2b --- /dev/null +++ b/qa/rest-compat-tests/src/test/resources/rest-api-spec/test/indices.get_field_mapping/51_field_wildcards_with_types.yml @@ -0,0 +1,181 @@ +--- +setup: + - do: + indices.create: + include_type_name: true + index: test_index + body: + mappings: + test_type: + properties: + t1: + type: text + t2: + type: text + obj: + properties: + t1: + type: text + i_t1: + type: text + i_t3: + type: text + + - do: + indices.create: + include_type_name: true + index: test_index_2 + body: + mappings: + test_type_2: + properties: + t1: + type: text + t2: + type: text + obj: + properties: + t1: + type: text + i_t1: + type: text + i_t3: + type: text + +--- +"Get field mapping with * for fields": + + - do: + indices.get_field_mapping: + include_type_name: true + fields: "*" + #- match: {test_index.mappings.test_type.t1.full_name: t1 } + #- match: {test_index.mappings.test_type.t2.full_name: t2 } + #- match: {test_index.mappings.test_type.obj\.t1.full_name: obj.t1 } + #- match: {test_index.mappings.test_type.obj\.i_t1.full_name: obj.i_t1 } + #- match: {test_index.mappings.test_type.obj\.i_t3.full_name: obj.i_t3 } + + - match: {test_index.mappings._doc.t1.full_name: t1 } + - match: {test_index.mappings._doc.t2.full_name: t2 } + - match: {test_index.mappings._doc.obj\.t1.full_name: obj.t1 } + - match: {test_index.mappings._doc.obj\.i_t1.full_name: obj.i_t1 } + - match: {test_index.mappings._doc.obj\.i_t3.full_name: obj.i_t3 } + +--- +"Get field mapping with t* for fields": + + - do: + indices.get_field_mapping: + include_type_name: true + index: test_index + fields: "t*" + #- match: {test_index.mappings.test_type.t1.full_name: t1 } + #- match: {test_index.mappings.test_type.t2.full_name: t2 } + #- length: {test_index.mappings.test_type: 2} + - match: {test_index.mappings._doc.t1.full_name: t1 } + - match: {test_index.mappings._doc.t2.full_name: t2 } + - length: {test_index.mappings._doc: 2} + +--- +"Get field mapping with *t1 for fields": + + - do: + indices.get_field_mapping: + include_type_name: true + index: test_index + fields: "*t1" + #- match: {test_index.mappings.test_type.t1.full_name: t1 } + #- match: {test_index.mappings.test_type.obj\.t1.full_name: obj.t1 } + #- match: {test_index.mappings.test_type.obj\.i_t1.full_name: obj.i_t1 } + #- length: {test_index.mappings.test_type: 3} + + - match: {test_index.mappings._doc.t1.full_name: t1 } + - match: {test_index.mappings._doc.obj\.t1.full_name: obj.t1 } + - match: {test_index.mappings._doc.obj\.i_t1.full_name: obj.i_t1 } + - length: {test_index.mappings._doc: 3} + +--- +"Get field mapping with wildcarded relative names": + + - do: + indices.get_field_mapping: + include_type_name: true + index: test_index + fields: "obj.i_*" + #- match: {test_index.mappings.test_type.obj\.i_t1.full_name: obj.i_t1 } + #- match: {test_index.mappings.test_type.obj\.i_t3.full_name: obj.i_t3 } + #- length: {test_index.mappings.test_type: 2} + + - match: {test_index.mappings._doc.obj\.i_t1.full_name: obj.i_t1 } + - match: {test_index.mappings._doc.obj\.i_t3.full_name: obj.i_t3 } + - length: {test_index.mappings._doc: 2} + +--- +"Get field mapping should work using '_all' for indices and types": + + - do: + indices.get_field_mapping: + include_type_name: true + index: _all + type: _all + fields: "t*" + #- match: {test_index.mappings.test_type.t1.full_name: t1 } + #- match: {test_index.mappings.test_type.t2.full_name: t2 } + #- length: {test_index.mappings.test_type: 2} + #- match: {test_index_2.mappings.test_type_2.t1.full_name: t1 } + #- match: {test_index_2.mappings.test_type_2.t2.full_name: t2 } + #- length: {test_index_2.mappings.test_type_2: 2} + + - match: {test_index.mappings._doc.t1.full_name: t1 } + - match: {test_index.mappings._doc.t2.full_name: t2 } + - length: {test_index.mappings._doc: 2} + - match: {test_index_2.mappings._doc.t1.full_name: t1 } + - match: {test_index_2.mappings._doc.t2.full_name: t2 } + - length: {test_index_2.mappings._doc: 2} + +--- +"Get field mapping should work using '*' for indices and types": + + - do: + indices.get_field_mapping: + include_type_name: true + index: '*' + type: '*' + fields: "t*" + #- match: {test_index.mappings.test_type.t1.full_name: t1 } + #- match: {test_index.mappings.test_type.t2.full_name: t2 } + #- length: {test_index.mappings.test_type: 2} + #- match: {test_index_2.mappings.test_type_2.t1.full_name: t1 } + #- match: {test_index_2.mappings.test_type_2.t2.full_name: t2 } + #- length: {test_index_2.mappings.test_type_2: 2} + + - match: {test_index.mappings._doc.t1.full_name: t1 } + - match: {test_index.mappings._doc.t2.full_name: t2 } + - length: {test_index.mappings._doc: 2} + - match: {test_index_2.mappings._doc.t1.full_name: t1 } + - match: {test_index_2.mappings._doc.t2.full_name: t2 } + - length: {test_index_2.mappings._doc: 2} + +--- +"Get field mapping should work using comma_separated values for indices and types": + + - do: + indices.get_field_mapping: + include_type_name: true + index: 'test_index,test_index_2' + type: 'test_type,test_type_2' + fields: "t*" + #- match: {test_index.mappings.test_type.t1.full_name: t1 } + #- match: {test_index.mappings.test_type.t2.full_name: t2 } + #- length: {test_index.mappings.test_type: 2} + #- match: {test_index_2.mappings.test_type_2.t1.full_name: t1 } + #- match: {test_index_2.mappings.test_type_2.t2.full_name: t2 } + #- length: {test_index_2.mappings.test_type_2: 2} + + - match: {test_index.mappings._doc.t1.full_name: t1 } + - match: {test_index.mappings._doc.t2.full_name: t2 } + - length: {test_index.mappings._doc: 2} + - match: {test_index_2.mappings._doc.t1.full_name: t1 } + - match: {test_index_2.mappings._doc.t2.full_name: t2 } + - length: {test_index_2.mappings._doc: 2} + diff --git a/qa/rest-compat-tests/src/test/resources/rest-api-spec/test/indices.get_mapping/11_basic_with_types.yml b/qa/rest-compat-tests/src/test/resources/rest-api-spec/test/indices.get_mapping/11_basic_with_types.yml new file mode 100644 index 0000000000000..188fb174af9fe --- /dev/null +++ b/qa/rest-compat-tests/src/test/resources/rest-api-spec/test/indices.get_mapping/11_basic_with_types.yml @@ -0,0 +1,175 @@ +--- +setup: + - do: + indices.create: + include_type_name: true + index: test_1 + body: + mappings: + doc: {} + - do: + indices.create: + include_type_name: true + index: test_2 + body: + mappings: + doc: {} +--- +"Get /{index}/_mapping with empty mappings": +#this is not working atm. index created with a type does not store that information +# hence there is no difference in this scenario and the test below. + - do: + indices.create: + index: t + + - do: + indices.get_mapping: + include_type_name: true + index: t + + - match: { t.mappings: {}} + +--- +"Get /_mapping": + + - do: + indices.get_mapping: + include_type_name: true + +# - is_true: test_1.mappings.doc + - is_true: test_1.mappings._doc +# - is_true: test_2.mappings.doc + - is_true: test_2.mappings._doc + +--- +"Get /{index}/_mapping": + + - do: + indices.get_mapping: + include_type_name: true + index: test_1 + +# - is_true: test_1.mappings.doc + - is_true: test_1.mappings._doc + - is_false: test_2 + + +--- +"Get /{index}/_mapping/_all": + + - do: + indices.get_mapping: + include_type_name: true + index: test_1 + type: _all + +# - is_true: test_1.mappings.doc + - is_true: test_1.mappings._doc + - is_false: test_2 + +--- +"Get /{index}/_mapping/*": + + - do: + indices.get_mapping: + include_type_name: true + index: test_1 + type: '*' + +# - is_true: test_1.mappings.doc + - is_true: test_1.mappings._doc + - is_false: test_2 + +--- +"Get /{index}/_mapping/{type}": + + - do: + indices.get_mapping: + include_type_name: true + index: test_1 + type: doc + +# - is_true: test_1.mappings.doc + - is_true: test_1.mappings._doc + - is_false: test_2 + +--- +"Get /{index}/_mapping/{type*}": + + - do: + indices.get_mapping: + include_type_name: true + index: test_1 + type: 'd*' + +# - is_true: test_1.mappings.doc + - is_true: test_1.mappings._doc + - is_false: test_2 + +--- +"Get /_mapping/{type}": + + - do: + indices.get_mapping: + include_type_name: true + type: doc + +# - is_true: test_1.mappings.doc + - is_true: test_1.mappings._doc +# - is_true: test_2.mappings.doc + - is_true: test_2.mappings._doc + +--- +"Get /_all/_mapping/{type}": + + - do: + indices.get_mapping: + include_type_name: true + index: _all + type: doc + +# - is_true: test_1.mappings.doc + - is_true: test_1.mappings._doc +# - is_true: test_2.mappings.doc + - is_true: test_2.mappings._doc + +--- +"Get /*/_mapping/{type}": + + - do: + indices.get_mapping: + include_type_name: true + index: '*' + type: doc + +# - is_true: test_1.mappings.doc + - is_true: test_1.mappings._doc +# - is_true: test_2.mappings.doc + - is_true: test_2.mappings._doc + +--- +"Get /index,index/_mapping/{type}": + + - do: + indices.get_mapping: + include_type_name: true + index: test_1,test_2 + type: doc + +# - is_true: test_1.mappings.doc + - is_true: test_1.mappings._doc +# - is_true: test_2.mappings.doc + - is_true: test_2.mappings._doc + +--- +"Get /index*/_mapping/{type}": + + - do: + indices.get_mapping: + include_type_name: true + index: '*2' + type: doc + +# - is_true: test_2.mappings.doc + - is_true: test_2.mappings._doc + - is_false: test_1 diff --git a/qa/rest-compat-tests/src/test/resources/rest-api-spec/test/indices.put_mapping/11_basic_with_types.yml b/qa/rest-compat-tests/src/test/resources/rest-api-spec/test/indices.put_mapping/11_basic_with_types.yml new file mode 100644 index 0000000000000..6b16b01b465e3 --- /dev/null +++ b/qa/rest-compat-tests/src/test/resources/rest-api-spec/test/indices.put_mapping/11_basic_with_types.yml @@ -0,0 +1,79 @@ +--- +"Test Create and update mapping": + - do: + indices.create: + index: test_index + + - do: + indices.put_mapping: + include_type_name: true + index: test_index + type: test_type + body: + test_type: + properties: + text1: + type: text + analyzer: whitespace + text2: + type: text + analyzer: whitespace + subfield.text3: + type: text + + - do: + indices.get_mapping: + include_type_name: true + index: test_index + #- match: {test_index.mappings.test_type.properties.text1.type: text} + #- match: {test_index.mappings.test_type.properties.text1.analyzer: whitespace} + #- match: {test_index.mappings.test_type.properties.text2.type: text} + #- match: {test_index.mappings.test_type.properties.text2.analyzer: whitespace} + - match: {test_index.mappings._doc.properties.text1.type: text} + - match: {test_index.mappings._doc.properties.text1.analyzer: whitespace} + - match: {test_index.mappings._doc.properties.text2.type: text} + - match: {test_index.mappings._doc.properties.text2.analyzer: whitespace} + + - do: + indices.put_mapping: + include_type_name: true + index: test_index + type: test_type + body: + test_type: + properties: + text1: + type: text + analyzer: whitespace + fields: + text_raw: + type: keyword + + + - do: + indices.get_mapping: + include_type_name: true + index: test_index + #- match: {test_index.mappings.test_type.properties.text1.type: text} + # - match: {test_index.mappings.test_type.properties.subfield.properties.text3.type: text} + # - match: {test_index.mappings.test_type.properties.text1.fields.text_raw.type: keyword} + - match: {test_index.mappings._doc.properties.text1.type: text} + - match: {test_index.mappings._doc.properties.subfield.properties.text3.type: text} + - match: {test_index.mappings._doc.properties.text1.fields.text_raw.type: keyword} + +--- +"Create index with invalid mappings": + - do: + indices.create: + index: test_index + - do: + catch: /illegal_argument_exception/ + indices.put_mapping: + include_type_name: true + index: test_index + type: test_type + body: + test_type: + properties: + "": + type: keyword diff --git a/qa/rest-compat-tests/src/test/resources/rest-api-spec/test/indices.put_mapping/all_path_options_with_types.yml b/qa/rest-compat-tests/src/test/resources/rest-api-spec/test/indices.put_mapping/all_path_options_with_types.yml new file mode 100644 index 0000000000000..b2ad5ef665f39 --- /dev/null +++ b/qa/rest-compat-tests/src/test/resources/rest-api-spec/test/indices.put_mapping/all_path_options_with_types.yml @@ -0,0 +1,263 @@ +setup: + - do: + indices.create: + index: test_index1 + - do: + indices.create: + index: test_index2 + - do: + indices.create: + index: foo + + +--- +"put one mapping per index": + - do: + indices.put_mapping: + include_type_name: true + index: test_index1 + type: test_type + body: + test_type: + properties: + text: + type: text + analyzer: whitespace + - do: + indices.put_mapping: + include_type_name: true + index: test_index2 + type: test_type + body: + test_type: + properties: + text: + type: text + analyzer: whitespace + + + - do: + indices.get_mapping: + include_type_name: true +#- match: {test_index1.mappings.test_type.properties.text.type: text} +# - match: {test_index1.mappings.test_type.properties.text.analyzer: whitespace} +# +# - match: {test_index2.mappings.test_type.properties.text.type: text} +# - match: {test_index2.mappings.test_type.properties.text.analyzer: whitespace} + - match: {test_index1.mappings._doc.properties.text.type: text} + - match: {test_index1.mappings._doc.properties.text.analyzer: whitespace} + + - match: {test_index2.mappings._doc.properties.text.type: text} + - match: {test_index2.mappings._doc.properties.text.analyzer: whitespace} + +# - match: { foo.mappings: {} } + - match: { 'foo.mappings': { '_doc':{}}} + +--- +"put mapping in _all index": + + - do: + indices.put_mapping: + include_type_name: true + index: _all + type: test_type + body: + test_type: + properties: + text: + type: text + analyzer: whitespace + + - do: + indices.get_mapping: + include_type_name: true +#- match: {test_index1.mappings.test_type.properties.text.type: text} +# - match: {test_index1.mappings.test_type.properties.text.analyzer: whitespace} +# +# - match: {test_index2.mappings.test_type.properties.text.type: text} +# - match: {test_index2.mappings.test_type.properties.text.analyzer: whitespace} +# +# - match: {foo.mappings.test_type.properties.text.type: text} +# - match: {foo.mappings.test_type.properties.text.analyzer: whitespace} + - match: {test_index1.mappings._doc.properties.text.type: text} + - match: {test_index1.mappings._doc.properties.text.analyzer: whitespace} + + - match: {test_index2.mappings._doc.properties.text.type: text} + - match: {test_index2.mappings._doc.properties.text.analyzer: whitespace} + + - match: {foo.mappings._doc.properties.text.type: text} + - match: {foo.mappings._doc.properties.text.analyzer: whitespace} + +--- +"put mapping in * index": + - do: + indices.put_mapping: + include_type_name: true + index: "*" + type: test_type + body: + test_type: + properties: + text: + type: text + analyzer: whitespace + + - do: + indices.get_mapping: + include_type_name: true +#- match: {test_index1.mappings.test_type.properties.text.type: text} +# - match: {test_index1.mappings.test_type.properties.text.analyzer: whitespace} +# +# - match: {test_index2.mappings.test_type.properties.text.type: text} +# - match: {test_index2.mappings.test_type.properties.text.analyzer: whitespace} +# +# - match: {foo.mappings.test_type.properties.text.type: text} +# - match: {foo.mappings.test_type.properties.text.analyzer: whitespace} + - match: {test_index1.mappings._doc.properties.text.type: text} + - match: {test_index1.mappings._doc.properties.text.analyzer: whitespace} + + - match: {test_index2.mappings._doc.properties.text.type: text} + - match: {test_index2.mappings._doc.properties.text.analyzer: whitespace} + + - match: {foo.mappings._doc.properties.text.type: text} + - match: {foo.mappings._doc.properties.text.analyzer: whitespace} + +--- +"put mapping in prefix* index": + - do: + indices.put_mapping: + include_type_name: true + index: "test_index*" + type: test_type + body: + test_type: + properties: + text: + type: text + analyzer: whitespace + + - do: + indices.get_mapping: + include_type_name: true +#- match: {test_index1.mappings.test_type.properties.text.type: text} +# - match: {test_index1.mappings.test_type.properties.text.analyzer: whitespace} +# +# - match: {test_index2.mappings.test_type.properties.text.type: text} +# - match: {test_index2.mappings.test_type.properties.text.analyzer: whitespace} + - match: {test_index1.mappings._doc.properties.text.type: text} + - match: {test_index1.mappings._doc.properties.text.analyzer: whitespace} + + - match: {test_index2.mappings._doc.properties.text.type: text} + - match: {test_index2.mappings._doc.properties.text.analyzer: whitespace} + +# - match: { foo.mappings: {} } + - match: { 'foo.mappings': { '_doc':{}}} + +--- +"put mapping in list of indices": + - do: + indices.put_mapping: + include_type_name: true + index: [test_index1, test_index2] + type: test_type + body: + test_type: + properties: + text: + type: text + analyzer: whitespace + + - do: + indices.get_mapping: + include_type_name: true +#- match: {test_index1.mappings.test_type.properties.text.type: text} +# - match: {test_index1.mappings.test_type.properties.text.analyzer: whitespace} +# +# - match: {test_index2.mappings.test_type.properties.text.type: text} +# - match: {test_index2.mappings.test_type.properties.text.analyzer: whitespace} + - match: {test_index1.mappings._doc.properties.text.type: text} + - match: {test_index1.mappings._doc.properties.text.analyzer: whitespace} + + - match: {test_index2.mappings._doc.properties.text.type: text} + - match: {test_index2.mappings._doc.properties.text.analyzer: whitespace} + +# - match: { foo.mappings: {} } + - match: { 'foo.mappings': { '_doc':{}}} + +--- +"put mapping with blank index": + - do: + indices.put_mapping: + include_type_name: true + type: test_type + body: + test_type: + properties: + text: + type: text + analyzer: whitespace + + - do: + indices.get_mapping: + include_type_name: true +#- match: {test_index1.mappings.test_type.properties.text.type: text} +# - match: {test_index1.mappings.test_type.properties.text.analyzer: whitespace} +# +# - match: {test_index2.mappings.test_type.properties.text.type: text} +# - match: {test_index2.mappings.test_type.properties.text.analyzer: whitespace} +# +# - match: {foo.mappings.test_type.properties.text.type: text} +# - match: {foo.mappings.test_type.properties.text.analyzer: whitespace} + - match: {test_index1.mappings._doc.properties.text.type: text} + - match: {test_index1.mappings._doc.properties.text.analyzer: whitespace} + + - match: {test_index2.mappings._doc.properties.text.type: text} + - match: {test_index2.mappings._doc.properties.text.analyzer: whitespace} + + - match: {foo.mappings._doc.properties.text.type: text} + - match: {foo.mappings._doc.properties.text.analyzer: whitespace} + +--- +"put mapping with missing type": + + + - do: + catch: param + indices.put_mapping: + include_type_name: true + +--- +"post a mapping with default analyzer twice": + + - do: + indices.put_mapping: + include_type_name: true + index: test_index1 + type: test_type + body: + test_type: + dynamic: false + properties: + text: + analyzer: default + type: text + + - do: + indices.put_mapping: + include_type_name: true + index: test_index1 + type: test_type + body: + test_type: + dynamic: false + properties: + text: + analyzer: default + type: text + + - do: + indices.get_mapping: + include_type_name: true +# - match: {test_index1.mappings.test_type.properties.text.type: text} + - match: {test_index1.mappings._doc.properties.text.type: text} + diff --git a/server/src/main/java/org/elasticsearch/Version.java b/server/src/main/java/org/elasticsearch/Version.java index 7161703efbf0c..8d0bc0364551c 100644 --- a/server/src/main/java/org/elasticsearch/Version.java +++ b/server/src/main/java/org/elasticsearch/Version.java @@ -344,6 +344,10 @@ public boolean isCompatible(Version version) { return compatible; } + public static Version minimumRestCompatibilityVersion(){ + return Version.V_7_0_0; + } + @SuppressForbidden(reason = "System.out.*") public static void main(String[] args) { final String versionOutput = String.format( diff --git a/server/src/main/java/org/elasticsearch/action/DocWriteResponse.java b/server/src/main/java/org/elasticsearch/action/DocWriteResponse.java index 9ae090cbd6e4c..1d73dfd212363 100644 --- a/server/src/main/java/org/elasticsearch/action/DocWriteResponse.java +++ b/server/src/main/java/org/elasticsearch/action/DocWriteResponse.java @@ -53,6 +53,8 @@ */ public abstract class DocWriteResponse extends ReplicationResponse implements WriteResponse, StatusToXContentObject { + static final String TYPE_FIELD_NAME = "_type"; + private static final String _SHARDS = "_shards"; private static final String _INDEX = "_index"; private static final String _ID = "_id"; @@ -304,6 +306,9 @@ private void writeWithoutShardId(StreamOutput out) throws IOException { @Override public final XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); + if (builder.getCompatibleMajorVersion() == Version.V_7_0_0.major) { + builder.field(TYPE_FIELD_NAME, MapperService.SINGLE_MAPPING_NAME); + } innerToXContent(builder, params); builder.endObject(); return builder; diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/mapping/get/GetFieldMappingsResponse.java b/server/src/main/java/org/elasticsearch/action/admin/indices/mapping/get/GetFieldMappingsResponse.java index a6358e388a63e..392c78d08ed39 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/mapping/get/GetFieldMappingsResponse.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/mapping/get/GetFieldMappingsResponse.java @@ -102,6 +102,8 @@ public FieldMappingMetadata fieldMappings(String index, String field) { } return indexMapping.get(field); } + public static final String INCLUDE_TYPE_NAME_PARAMETER = "include_type_name"; + public static final boolean DEFAULT_INCLUDE_TYPE_NAME_POLICY = false; @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { @@ -109,9 +111,16 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws for (Map.Entry> indexEntry : mappings.entrySet()) { builder.startObject(indexEntry.getKey()); builder.startObject(MAPPINGS.getPreferredName()); - + boolean includeTypeName = params.paramAsBoolean(INCLUDE_TYPE_NAME_PARAMETER, + DEFAULT_INCLUDE_TYPE_NAME_POLICY); if (indexEntry.getValue() != null) { + if (builder.getCompatibleMajorVersion() == Version.V_7_0_0.major && includeTypeName) { + builder.startObject(MapperService.SINGLE_MAPPING_NAME); + } addFieldMappingsToBuilder(builder, params, indexEntry.getValue()); + if (builder.getCompatibleMajorVersion() == Version.V_7_0_0.major && includeTypeName) { + builder.endObject(); + } } builder.endObject(); diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/mapping/get/GetMappingsResponse.java b/server/src/main/java/org/elasticsearch/action/admin/indices/mapping/get/GetMappingsResponse.java index 36b9cf165dae1..f1e1c59092d65 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/mapping/get/GetMappingsResponse.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/mapping/get/GetMappingsResponse.java @@ -34,6 +34,9 @@ import java.io.IOException; +import static org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse.DEFAULT_INCLUDE_TYPE_NAME_POLICY; +import static org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse.INCLUDE_TYPE_NAME_PARAMETER; + public class GetMappingsResponse extends ActionResponse implements ToXContentFragment { private static final ParseField MAPPINGS = new ParseField("mappings"); @@ -100,7 +103,14 @@ public void writeTo(StreamOutput out) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { for (final ObjectObjectCursor indexEntry : getMappings()) { builder.startObject(indexEntry.key); - if (indexEntry.value != null) { + boolean includeTypeName = params.paramAsBoolean(INCLUDE_TYPE_NAME_PARAMETER, + DEFAULT_INCLUDE_TYPE_NAME_POLICY); + if (builder.getCompatibleMajorVersion() == Version.V_7_0_0.major + && includeTypeName) { + builder.startObject(MAPPINGS.getPreferredName()); + builder.field(MapperService.SINGLE_MAPPING_NAME, indexEntry.value.sourceAsMap()); + builder.endObject(); + } else if (indexEntry.value != null) { builder.field(MAPPINGS.getPreferredName(), indexEntry.value.sourceAsMap()); } else { builder.startObject(MAPPINGS.getPreferredName()).endObject(); diff --git a/server/src/main/java/org/elasticsearch/action/search/MultiSearchRequest.java b/server/src/main/java/org/elasticsearch/action/search/MultiSearchRequest.java index 2fba92f5bab26..ce570731dc10f 100644 --- a/server/src/main/java/org/elasticsearch/action/search/MultiSearchRequest.java +++ b/server/src/main/java/org/elasticsearch/action/search/MultiSearchRequest.java @@ -43,6 +43,7 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.function.Function; import static org.elasticsearch.action.ValidateActions.addValidationError; import static org.elasticsearch.common.xcontent.support.XContentMapValues.nodeBooleanValue; @@ -177,7 +178,8 @@ public static void readMultiLineFormat(BytesReference data, String searchType, Boolean ccsMinimizeRoundtrips, NamedXContentRegistry registry, - boolean allowExplicitIndex) throws IOException { + boolean allowExplicitIndex, + Function typeConsumer) throws IOException { int from = 0; byte marker = xContent.streamSeparator(); while (true) { @@ -239,7 +241,9 @@ public static void readMultiLineFormat(BytesReference data, } else if ("ignore_throttled".equals(entry.getKey()) || "ignoreThrottled".equals(entry.getKey())) { ignoreThrottled = value; } else { - throw new IllegalArgumentException("key [" + entry.getKey() + "] is not supported in the metadata section"); + if(typeConsumer.apply(entry.getKey()) == false){ + throw new IllegalArgumentException("key [" + entry.getKey() + "] is not supported in the metadata section"); + } } } defaultOptions = IndicesOptions.fromParameters(expandWildcards, ignoreUnavailable, allowNoIndices, ignoreThrottled, diff --git a/server/src/main/java/org/elasticsearch/action/termvectors/MultiTermVectorsRequest.java b/server/src/main/java/org/elasticsearch/action/termvectors/MultiTermVectorsRequest.java index 056947484a0f5..16e558ec8d9aa 100644 --- a/server/src/main/java/org/elasticsearch/action/termvectors/MultiTermVectorsRequest.java +++ b/server/src/main/java/org/elasticsearch/action/termvectors/MultiTermVectorsRequest.java @@ -37,6 +37,7 @@ import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.function.Function; public class MultiTermVectorsRequest extends ActionRequest implements Iterable, CompositeIndicesRequest, RealtimeRequest { @@ -98,8 +99,11 @@ public boolean isEmpty() { public List getRequests() { return requests; } - public void add(TermVectorsRequest template, @Nullable XContentParser parser) throws IOException { + add(template, parser, k->false); + } + public void add(TermVectorsRequest template, @Nullable XContentParser parser, Function typeConsumer) + throws IOException { XContentParser.Token token; String currentFieldName = null; if (parser != null) { @@ -113,7 +117,7 @@ public void add(TermVectorsRequest template, @Nullable XContentParser parser) th throw new IllegalArgumentException("docs array element should include an object"); } TermVectorsRequest termVectorsRequest = new TermVectorsRequest(template); - TermVectorsRequest.parseRequest(termVectorsRequest, parser); + TermVectorsRequest.parseRequest(termVectorsRequest, parser, typeConsumer); add(termVectorsRequest); } } else if ("ids".equals(currentFieldName)) { diff --git a/server/src/main/java/org/elasticsearch/action/termvectors/TermVectorsRequest.java b/server/src/main/java/org/elasticsearch/action/termvectors/TermVectorsRequest.java index 09f7a15dff795..0bc7259206b91 100644 --- a/server/src/main/java/org/elasticsearch/action/termvectors/TermVectorsRequest.java +++ b/server/src/main/java/org/elasticsearch/action/termvectors/TermVectorsRequest.java @@ -48,6 +48,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; @@ -528,11 +529,16 @@ public enum Flag { // the ordinal for encoding! Only append to the end! Positions, Offsets, Payloads, FieldStatistics, TermStatistics } + public static void parseRequest(TermVectorsRequest termVectorsRequest, XContentParser parser) throws IOException { + parseRequest(termVectorsRequest, parser, k -> false); + } /** * populates a request object (pre-populated with defaults) based on a parser. */ - public static void parseRequest(TermVectorsRequest termVectorsRequest, XContentParser parser) throws IOException { + public static void parseRequest(TermVectorsRequest termVectorsRequest, + XContentParser parser, + Function typeConsumer) throws IOException { XContentParser.Token token; String currentFieldName = null; List fields = new ArrayList<>(); @@ -585,7 +591,7 @@ public static void parseRequest(TermVectorsRequest termVectorsRequest, XContentP termVectorsRequest.version = parser.longValue(); } else if (VERSION_TYPE.match(currentFieldName, parser.getDeprecationHandler())) { termVectorsRequest.versionType = VersionType.fromString(parser.text()); - } else { + } else if(typeConsumer.apply(currentFieldName) == false) { throw new ElasticsearchParseException("failed to parse term vectors request. unknown field [{}]", currentFieldName); } } diff --git a/server/src/main/java/org/elasticsearch/http/AbstractHttpServerTransport.java b/server/src/main/java/org/elasticsearch/http/AbstractHttpServerTransport.java index 204c9ad0365b6..e146e383f9e91 100644 --- a/server/src/main/java/org/elasticsearch/http/AbstractHttpServerTransport.java +++ b/server/src/main/java/org/elasticsearch/http/AbstractHttpServerTransport.java @@ -341,6 +341,9 @@ private void handleIncomingRequest(final HttpRequest httpRequest, final HttpChan } catch (final RestRequest.BadParameterException e) { badRequestCause = ExceptionsHelper.useOrSuppress(badRequestCause, e); innerRestRequest = RestRequest.requestWithoutParameters(xContentRegistry, httpRequest, httpChannel); + } catch (final RestRequest.CompatibleApiHeadersCombinationException e){ + badRequestCause = ExceptionsHelper.useOrSuppress(badRequestCause, e); + innerRestRequest = RestRequest.requestNoValidation(xContentRegistry, httpRequest, httpChannel); } restRequest = innerRestRequest; } @@ -373,12 +376,11 @@ private void handleIncomingRequest(final HttpRequest httpRequest, final HttpChan } private RestRequest requestWithoutContentTypeHeader(HttpRequest httpRequest, HttpChannel httpChannel, Exception badRequestCause) { - HttpRequest httpRequestWithoutContentType = httpRequest.removeHeader("Content-Type"); try { - return RestRequest.request(xContentRegistry, httpRequestWithoutContentType, httpChannel); + return RestRequest.requestWithoutContentType(xContentRegistry, httpRequest, httpChannel); } catch (final RestRequest.BadParameterException e) { badRequestCause.addSuppressed(e); - return RestRequest.requestWithoutParameters(xContentRegistry, httpRequestWithoutContentType, httpChannel); + return RestRequest.requestNoValidation(xContentRegistry, httpRequest, httpChannel); } } } diff --git a/server/src/main/java/org/elasticsearch/index/get/GetResult.java b/server/src/main/java/org/elasticsearch/index/get/GetResult.java index 138d46f168846..1f5e4890e5f3a 100644 --- a/server/src/main/java/org/elasticsearch/index/get/GetResult.java +++ b/server/src/main/java/org/elasticsearch/index/get/GetResult.java @@ -28,6 +28,7 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.text.Text; import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentHelper; @@ -51,6 +52,9 @@ public class GetResult implements Writeable, Iterable, ToXContentObject { + private static final String TYPE_FIELD_NAME = "_type"; + private static final Text SINGLE_MAPPING_TYPE = new Text(MapperService.SINGLE_MAPPING_NAME); + public static final String _INDEX = "_index"; public static final String _ID = "_id"; private static final String _VERSION = "_version"; @@ -301,6 +305,9 @@ public XContentBuilder toXContentEmbedded(XContentBuilder builder, Params params public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); builder.field(_INDEX, index); + if (builder.getCompatibleMajorVersion() == Version.V_7_0_0.major) { + builder.field(TYPE_FIELD_NAME, SINGLE_MAPPING_TYPE); + } builder.field(_ID, id); if (isExists()) { if (version != -1) { diff --git a/server/src/main/java/org/elasticsearch/rest/AbstractRestChannel.java b/server/src/main/java/org/elasticsearch/rest/AbstractRestChannel.java index 467f1d969e8be..7a6abc34d6b60 100644 --- a/server/src/main/java/org/elasticsearch/rest/AbstractRestChannel.java +++ b/server/src/main/java/org/elasticsearch/rest/AbstractRestChannel.java @@ -127,6 +127,8 @@ public XContentBuilder newBuilder(@Nullable XContentType requestContentType, @Nu } builder.humanReadable(human); + + builder.setCompatibleMajorVersion(request.getCompatibleApiVersion().major); return builder; } diff --git a/server/src/main/java/org/elasticsearch/rest/BaseRestHandler.java b/server/src/main/java/org/elasticsearch/rest/BaseRestHandler.java index 101c3182fbb62..27a804507ff7a 100644 --- a/server/src/main/java/org/elasticsearch/rest/BaseRestHandler.java +++ b/server/src/main/java/org/elasticsearch/rest/BaseRestHandler.java @@ -148,6 +148,14 @@ protected final String unrecognized( return message.toString(); } + @Override + public String toString() { + return this.getClass()+"{" + + "name=" + this.getName() + ", " + + "compatibleWithVersion=" + this.compatibleWithVersion() + + '}'; + } + /** * REST requests are handled by preparing a channel consumer that represents the execution of * the request against a channel. diff --git a/server/src/main/java/org/elasticsearch/rest/CompatibleConstants.java b/server/src/main/java/org/elasticsearch/rest/CompatibleConstants.java new file mode 100644 index 0000000000000..9719383c3a3ed --- /dev/null +++ b/server/src/main/java/org/elasticsearch/rest/CompatibleConstants.java @@ -0,0 +1,30 @@ +/* + * 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.rest; + +public class CompatibleConstants { + + /** + * TODO revisit when https://github.com/elastic/elasticsearch/issues/52370 is resolved + */ + public static final String COMPATIBLE_ACCEPT_HEADER = "Accept"; + public static final String COMPATIBLE_CONTENT_TYPE_HEADER = "Content-Type"; + +} diff --git a/server/src/main/java/org/elasticsearch/rest/MethodHandlers.java b/server/src/main/java/org/elasticsearch/rest/MethodHandlers.java index 0d6233e62f925..a68ad4fa132b2 100644 --- a/server/src/main/java/org/elasticsearch/rest/MethodHandlers.java +++ b/server/src/main/java/org/elasticsearch/rest/MethodHandlers.java @@ -19,6 +19,7 @@ package org.elasticsearch.rest; +import org.elasticsearch.Version; import org.elasticsearch.common.Nullable; import java.util.HashMap; @@ -31,13 +32,14 @@ final class MethodHandlers { private final String path; - private final Map methodHandlers; + private final Map> methodHandlers; - MethodHandlers(String path, RestHandler handler, RestRequest.Method... methods) { + MethodHandlers(String path, RestHandler handler, Version version, RestRequest.Method... methods) { this.path = path; this.methodHandlers = new HashMap<>(methods.length); for (RestRequest.Method method : methods) { - methodHandlers.put(method, handler); + methodHandlers.computeIfAbsent(method, k -> new HashMap<>()) + .put(version, handler); } } @@ -45,9 +47,10 @@ final class MethodHandlers { * Add a handler for an additional array of methods. Note that {@code MethodHandlers} * does not allow replacing the handler for an already existing method. */ - MethodHandlers addMethods(RestHandler handler, RestRequest.Method... methods) { + MethodHandlers addMethods(RestHandler handler, Version version, RestRequest.Method... methods) { for (RestRequest.Method method : methods) { - RestHandler existing = methodHandlers.putIfAbsent(method, handler); + RestHandler existing = methodHandlers.computeIfAbsent(method, k -> new HashMap<>()) + .putIfAbsent(version, handler); if (existing != null) { throw new IllegalArgumentException("Cannot replace existing handler for [" + path + "] for method: " + method); } @@ -56,11 +59,26 @@ MethodHandlers addMethods(RestHandler handler, RestRequest.Method... methods) { } /** - * Returns the handler for the given method or {@code null} if none exists. + * Return a handler for a given method and a version + * If a handler for a given version is not found, the handler for Version.CURRENT is returned. + * We only expect Version.CURRENT or Version.CURRENT -1. This is validated. + * + * Handlers can be registered under the same path and method, but will require to have different versions (CURRENT or CURRENT-1) + * + * //todo What if a handler was added in V8 but was not present in V7? + * + * @param method a REST method under which a handler was registered + * @param version a Version under which a handler was registered + * @return a handler */ @Nullable - RestHandler getHandler(RestRequest.Method method) { - return methodHandlers.get(method); + RestHandler getHandler(RestRequest.Method method, Version version) { + Map versionToHandlers = methodHandlers.get(method); + if (versionToHandlers == null) { + return null; //method not found + } + final RestHandler handler = versionToHandlers.get(version); + return handler != null || version.equals(Version.CURRENT) ? handler : versionToHandlers.get(Version.CURRENT); } /** diff --git a/server/src/main/java/org/elasticsearch/rest/RestController.java b/server/src/main/java/org/elasticsearch/rest/RestController.java index a88007398a449..1110cbe3e8cdd 100644 --- a/server/src/main/java/org/elasticsearch/rest/RestController.java +++ b/server/src/main/java/org/elasticsearch/rest/RestController.java @@ -23,6 +23,7 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.ParameterizedMessage; import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.Version; import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Strings; @@ -144,12 +145,17 @@ protected void registerWithDeprecatedHandler(RestRequest.Method method, String p * @param method GET, POST, etc. */ protected void registerHandler(RestRequest.Method method, String path, RestHandler handler) { + assert Version.minimumRestCompatibilityVersion() == handler.compatibleWithVersion() || + Version.CURRENT == handler.compatibleWithVersion() + : "REST API compatibility is only supported for version " + Version.minimumRestCompatibilityVersion().major; + if (handler instanceof BaseRestHandler) { usageService.addRestHandler((BaseRestHandler) handler); } + final Version version = handler.compatibleWithVersion(); final RestHandler maybeWrappedHandler = handlerWrapper.apply(handler); - handlers.insertOrUpdate(path, new MethodHandlers(path, maybeWrappedHandler, method), - (mHandlers, newMHandler) -> mHandlers.addMethods(maybeWrappedHandler, method)); + handlers.insertOrUpdate(path, new MethodHandlers(path, maybeWrappedHandler, version, method), + (mHandlers, newMHandler) -> mHandlers.addMethods(maybeWrappedHandler, version, method)); } /** @@ -294,6 +300,8 @@ private void tryAllHandlers(final RestRequest request, final RestChannel channel final String rawPath = request.rawPath(); final String uri = request.uri(); + Version version = request.getCompatibleApiVersion(); + final RestRequest.Method requestMethod; try { // Resolves the HTTP method and fails if the method is invalid @@ -306,7 +314,7 @@ private void tryAllHandlers(final RestRequest request, final RestChannel channel if (handlers == null) { handler = null; } else { - handler = handlers.getHandler(requestMethod); + handler = handlers.getHandler(requestMethod, version); } if (handler == null) { if (handleNoHandlerFound(rawPath, requestMethod, uri, channel)) { diff --git a/server/src/main/java/org/elasticsearch/rest/RestHandler.java b/server/src/main/java/org/elasticsearch/rest/RestHandler.java index 0c06a84df62bc..a0bdc7ef986a2 100644 --- a/server/src/main/java/org/elasticsearch/rest/RestHandler.java +++ b/server/src/main/java/org/elasticsearch/rest/RestHandler.java @@ -19,6 +19,7 @@ package org.elasticsearch.rest; +import org.elasticsearch.Version; import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.common.xcontent.XContent; import org.elasticsearch.rest.RestRequest.Method; @@ -89,6 +90,16 @@ default List replacedRoutes() { return Collections.emptyList(); } + /** + * Returns a version a handler is compatible with. + * This version is then used to math a handler with a request that specified a version. + * If no version is specified, handler is assumed to be compatible with Version.CURRENT + * @return a version + */ + default Version compatibleWithVersion(){ + return Version.CURRENT; + } + class Route { private final String path; diff --git a/server/src/main/java/org/elasticsearch/rest/RestRequest.java b/server/src/main/java/org/elasticsearch/rest/RestRequest.java index 512bf72e9c0d3..dcfd2f144dbe5 100644 --- a/server/src/main/java/org/elasticsearch/rest/RestRequest.java +++ b/server/src/main/java/org/elasticsearch/rest/RestRequest.java @@ -21,6 +21,7 @@ import org.apache.lucene.util.SetOnce; import org.elasticsearch.ElasticsearchParseException; +import org.elasticsearch.Version; import org.elasticsearch.common.Booleans; import org.elasticsearch.common.CheckedConsumer; import org.elasticsearch.common.Nullable; @@ -45,6 +46,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; @@ -68,12 +70,12 @@ public class RestRequest implements ToXContent.Params { private final Set consumedParams = new HashSet<>(); private final SetOnce xContentType = new SetOnce<>(); private final HttpChannel httpChannel; + private final long requestId; + private final Version compatibleApiVersion; private HttpRequest httpRequest; - private boolean contentConsumed = false; - private final long requestId; public boolean isContentConsumed() { return contentConsumed; @@ -86,6 +88,13 @@ protected RestRequest(NamedXContentRegistry xContentRegistry, Map params, String path, Map> headers, HttpRequest httpRequest, HttpChannel httpChannel, long requestId) { + this(xContentRegistry, params, path, headers, httpRequest, httpChannel, requestId, true); + + } + + private RestRequest(NamedXContentRegistry xContentRegistry, Map params, String path, + Map> headers, HttpRequest httpRequest, HttpChannel httpChannel, + long requestId, boolean headersValidation) { final XContentType xContentType; try { xContentType = parseContentType(headers.get("Content-Type")); @@ -102,6 +111,7 @@ private RestRequest(NamedXContentRegistry xContentRegistry, Map this.rawPath = path; this.headers = Collections.unmodifiableMap(headers); this.requestId = requestId; + this.compatibleApiVersion = addCompatibleParameter(headersValidation); } protected RestRequest(RestRequest restRequest) { @@ -135,6 +145,65 @@ public static RestRequest request(NamedXContentRegistry xContentRegistry, HttpRe requestIdGenerator.incrementAndGet()); } + private Version addCompatibleParameter(boolean headersValidation) { + if (headersValidation && isRequestingCompatibility()) { + return Version.minimumRestCompatibilityVersion(); + } else { + return Version.CURRENT; + } + } + + private boolean isRequestingCompatibility() { + String acceptHeader = header(CompatibleConstants.COMPATIBLE_ACCEPT_HEADER); + String aVersion = XContentType.parseVersion(acceptHeader); + byte acceptVersion = aVersion == null ? Version.CURRENT.major : Integer.valueOf(aVersion).byteValue(); + String contentTypeHeader = header(CompatibleConstants.COMPATIBLE_CONTENT_TYPE_HEADER); + String cVersion = XContentType.parseVersion(contentTypeHeader); + byte contentTypeVersion = cVersion == null ? Version.CURRENT.major : Integer.valueOf(cVersion).byteValue(); + + if(Version.CURRENT.major < acceptVersion || Version.CURRENT.major - acceptVersion > 1 ){ + throw new CompatibleApiHeadersCombinationException( + String.format(Locale.ROOT, "Unsupported version provided. " + + "Accept=%s Content-Type=%s hasContent=%b path=%s params=%s method=%s", acceptHeader, + contentTypeHeader, hasContent(), path(), params.toString(), method().toString())); + } + if (hasContent()) { + if(Version.CURRENT.major < contentTypeVersion || Version.CURRENT.major - contentTypeVersion > 1 ){ + throw new CompatibleApiHeadersCombinationException( + String.format(Locale.ROOT, "Unsupported version provided. " + + "Accept=%s Content-Type=%s hasContent=%b path=%s params=%s method=%s", acceptHeader, + contentTypeHeader, hasContent(), path(), params.toString(), method().toString())); + } + + if (contentTypeVersion != acceptVersion) { + throw new CompatibleApiHeadersCombinationException( + String.format(Locale.ROOT, "Content-Type and Accept headers have to match when content is present. " + + "Accept=%s Content-Type=%s hasContent=%b path=%s params=%s method=%s", acceptHeader, + contentTypeHeader, hasContent(), path(), params.toString(), method().toString())); + } + // both headers should be versioned or none + if ((cVersion == null && aVersion!=null) || (aVersion ==null && cVersion!=null) ){ + throw new CompatibleApiHeadersCombinationException( + String.format(Locale.ROOT, "Versioning is required on both Content-Type and Accept headers. " + + "Accept=%s Content-Type=%s hasContent=%b path=%s params=%s method=%s", acceptHeader, + contentTypeHeader, hasContent(), path(), params.toString(), method().toString())); + } + + return contentTypeVersion < Version.CURRENT.major; + } + + return acceptVersion < Version.CURRENT.major; + } + + /** + * An http request can be accompanied with a compatible version indicating with what version a client is using. + * Only a major Versions are supported. Internally we use Versions objects, but only use Version(major,0,0) + * @return a version with what a client is compatible with. + */ + public Version getCompatibleApiVersion() { + return this.compatibleApiVersion; + } + private static Map params(final String uri) { final Map params = new HashMap<>(); int index = uri.indexOf('?'); @@ -170,9 +239,38 @@ public static RestRequest requestWithoutParameters(NamedXContentRegistry xConten HttpChannel httpChannel) { Map params = Collections.emptyMap(); return new RestRequest(xContentRegistry, params, httpRequest.uri(), httpRequest.getHeaders(), httpRequest, httpChannel, - requestIdGenerator.incrementAndGet()); + requestIdGenerator.incrementAndGet(), false); + } + + public static RestRequest requestWithoutContentType(NamedXContentRegistry xContentRegistry, HttpRequest httpRequest, + HttpChannel httpChannel) { + HttpRequest httpRequestWithoutContentType = httpRequest.removeHeader("Content-Type"); + Map params = params(httpRequest.uri()); + String path = path(httpRequest.uri()); + return new RestRequest(xContentRegistry, params, path, httpRequestWithoutContentType.getHeaders(), + httpRequestWithoutContentType, httpChannel, + requestIdGenerator.incrementAndGet(), false); + } + + /** + * creates a Rest request when it is not able to pass a validation but a response is needed to be returned. + * @param xContentRegistry the content registry + * @param httpRequest the http request + * @param httpChannel the http channel + * @return a RestRequest without headers and parameters + */ + public static RestRequest requestNoValidation(NamedXContentRegistry xContentRegistry, + HttpRequest httpRequest, + HttpChannel httpChannel) { + Map params = Collections.emptyMap(); + HttpRequest httpRequestWithoutContentType = httpRequest.removeHeader("Content-Type"); + + return new RestRequest(xContentRegistry, params, httpRequestWithoutContentType.uri(), + httpRequestWithoutContentType.getHeaders(), httpRequestWithoutContentType, httpChannel, + requestIdGenerator.incrementAndGet(), false); } + public enum Method { GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH, TRACE, CONNECT } @@ -538,4 +636,12 @@ public static class BadParameterException extends RuntimeException { } + public static class CompatibleApiHeadersCombinationException extends RuntimeException { + + CompatibleApiHeadersCombinationException(String cause) { + super(cause); + } + + } + } diff --git a/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestPutMappingAction.java b/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestPutMappingAction.java index 5baf9b8e9ffeb..8d306156ce82e 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestPutMappingAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestPutMappingAction.java @@ -55,14 +55,17 @@ public String getName() { @Override public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { - PutMappingRequest putMappingRequest = putMappingRequest(Strings.splitStringByCommaToArray(request.param("index"))); - Map sourceAsMap = XContentHelper.convertToMap(request.requiredContent(), false, request.getXContentType()).v2(); if (MapperService.isMappingSourceTyped(MapperService.SINGLE_MAPPING_NAME, sourceAsMap)) { throw new IllegalArgumentException("Types cannot be provided in put mapping requests"); } + return sendPutMappingRequest(request, client, sourceAsMap); + } + + protected RestChannelConsumer sendPutMappingRequest(RestRequest request, NodeClient client, Map sourceAsMap) { + PutMappingRequest putMappingRequest = putMappingRequest(Strings.splitStringByCommaToArray(request.param("index"))); putMappingRequest.source(sourceAsMap); putMappingRequest.timeout(request.paramAsTime("timeout", putMappingRequest.timeout())); putMappingRequest.masterNodeTimeout(request.paramAsTime("master_timeout", putMappingRequest.masterNodeTimeout())); diff --git a/server/src/main/java/org/elasticsearch/rest/action/document/RestGetAction.java b/server/src/main/java/org/elasticsearch/rest/action/document/RestGetAction.java index 82548a505b47e..138e8a0b35973 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/document/RestGetAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/document/RestGetAction.java @@ -77,7 +77,6 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC getRequest.versionType(VersionType.fromString(request.param("version_type"), getRequest.versionType())); getRequest.fetchSourceContext(FetchSourceContext.parseFromRestRequest(request)); - return channel -> client.get(getRequest, new RestToXContentListener(channel) { @Override protected RestStatus getStatus(final GetResponse response) { @@ -85,5 +84,4 @@ protected RestStatus getStatus(final GetResponse response) { } }); } - } diff --git a/server/src/main/java/org/elasticsearch/rest/action/document/RestIndexAction.java b/server/src/main/java/org/elasticsearch/rest/action/document/RestIndexAction.java index 6c54285d67dad..e8fa904a6ffda 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/document/RestIndexAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/document/RestIndexAction.java @@ -39,7 +39,6 @@ import static org.elasticsearch.rest.RestRequest.Method.PUT; public class RestIndexAction extends BaseRestHandler { - @Override public List routes() { return List.of( @@ -52,7 +51,7 @@ public String getName() { return "document_index_action"; } - public static final class CreateHandler extends RestIndexAction { + public static class CreateHandler extends RestIndexAction { @Override public String getName() { @@ -80,7 +79,7 @@ void validateOpType(String opType) { } } - public static final class AutoIdHandler extends RestIndexAction { + public static class AutoIdHandler extends RestIndexAction { private final Supplier nodesInCluster; diff --git a/server/src/main/java/org/elasticsearch/rest/action/search/RestMultiSearchAction.java b/server/src/main/java/org/elasticsearch/rest/action/search/RestMultiSearchAction.java index ee6cba31d26f1..19a32213f213c 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/search/RestMultiSearchAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/search/RestMultiSearchAction.java @@ -42,6 +42,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Function; import static org.elasticsearch.rest.RestRequest.Method.GET; import static org.elasticsearch.rest.RestRequest.Method.POST; @@ -57,7 +58,7 @@ public class RestMultiSearchAction extends BaseRestHandler { RESPONSE_PARAMS = Collections.unmodifiableSet(responseParams); } - private final boolean allowExplicitIndex; + protected final boolean allowExplicitIndex; public RestMultiSearchAction(Settings settings) { this.allowExplicitIndex = MULTI_ALLOW_EXPLICIT_INDEX.get(settings); @@ -83,10 +84,21 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC return channel -> client.multiSearch(multiSearchRequest, new RestToXContentListener<>(channel)); } + public static MultiSearchRequest parseRequest(RestRequest restRequest, boolean allowExplicitIndex) throws IOException { + return parseRequest(restRequest,allowExplicitIndex, key->false); + } + /** * Parses a {@link RestRequest} body and returns a {@link MultiSearchRequest} + * + * @param typeConsumer - is a function used when parsing a request body. if it contains a types field it will consume it, + * allowing the same parsing logic to work in v7 and v8. + * It takes a string - field name, returns a boolean - if the field was "type or types" */ - public static MultiSearchRequest parseRequest(RestRequest restRequest, boolean allowExplicitIndex) throws IOException { + public static MultiSearchRequest parseRequest(RestRequest restRequest, + boolean allowExplicitIndex, + Function typeConsumer + /*TODO rename to unexpected field consumer?*/) throws IOException { MultiSearchRequest multiRequest = new MultiSearchRequest(); IndicesOptions indicesOptions = IndicesOptions.fromRequest(restRequest, multiRequest.indicesOptions()); multiRequest.indicesOptions(indicesOptions); @@ -112,7 +124,7 @@ public static MultiSearchRequest parseRequest(RestRequest restRequest, boolean a searchRequest.source(SearchSourceBuilder.fromXContent(parser, false)); RestSearchAction.checkRestTotalHits(restRequest, searchRequest); multiRequest.add(searchRequest); - }); + }, typeConsumer); List requests = multiRequest.requests(); for (SearchRequest request : requests) { // preserve if it's set on the request @@ -129,8 +141,11 @@ public static MultiSearchRequest parseRequest(RestRequest restRequest, boolean a /** * Parses a multi-line {@link RestRequest} body, instantiating a {@link SearchRequest} for each line and applying the given consumer. */ - public static void parseMultiLineRequest(RestRequest request, IndicesOptions indicesOptions, boolean allowExplicitIndex, - CheckedBiConsumer consumer) throws IOException { + public static void parseMultiLineRequest(RestRequest request, + IndicesOptions indicesOptions, + boolean allowExplicitIndex, + CheckedBiConsumer consumer, + Function typeConsumer) throws IOException { String[] indices = Strings.splitStringByCommaToArray(request.param("index")); String searchType = request.param("search_type"); @@ -141,7 +156,7 @@ public static void parseMultiLineRequest(RestRequest request, IndicesOptions ind final XContent xContent = sourceTuple.v1().xContent(); final BytesReference data = sourceTuple.v2(); MultiSearchRequest.readMultiLineFormat(data, xContent, consumer, indices, indicesOptions, routing, - searchType, ccsMinimizeRoundtrips, request.getXContentRegistry(), allowExplicitIndex); + searchType, ccsMinimizeRoundtrips, request.getXContentRegistry(), allowExplicitIndex, typeConsumer); } @Override diff --git a/server/src/test/java/org/elasticsearch/action/search/MultiSearchRequestTests.java b/server/src/test/java/org/elasticsearch/action/search/MultiSearchRequestTests.java index 3cbf7e4ceea6f..716aa5ba9f410 100644 --- a/server/src/test/java/org/elasticsearch/action/search/MultiSearchRequestTests.java +++ b/server/src/test/java/org/elasticsearch/action/search/MultiSearchRequestTests.java @@ -229,7 +229,7 @@ private MultiSearchRequest parseMultiSearchRequest(String sample) throws IOExcep (searchRequest, parser) -> { searchRequest.source(SearchSourceBuilder.fromXContent(parser, false)); request.add(searchRequest); - }); + }, k->false); return request; } @@ -256,7 +256,8 @@ public void testMultiLineSerialization() throws IOException { parsedRequest.add(r); }; MultiSearchRequest.readMultiLineFormat(new BytesArray(originalBytes), xContentType.xContent(), - consumer, null, null, null, null, null, xContentRegistry(), true); + consumer, null, null, null, null, null, xContentRegistry(), true, + key -> false); assertEquals(originalRequest, parsedRequest); } } diff --git a/server/src/test/java/org/elasticsearch/common/xcontent/XContentTypeTests.java b/server/src/test/java/org/elasticsearch/common/xcontent/XContentTypeTests.java index 47a470e2cea84..3ceb93ef6abec 100644 --- a/server/src/test/java/org/elasticsearch/common/xcontent/XContentTypeTests.java +++ b/server/src/test/java/org/elasticsearch/common/xcontent/XContentTypeTests.java @@ -84,4 +84,58 @@ public void testFromRubbish() throws Exception { assertThat(XContentType.fromMediaTypeOrFormat("text/plain"), nullValue()); assertThat(XContentType.fromMediaTypeOrFormat("gobbly;goop"), nullValue()); } + + public void testMediaType() { + byte version = randomByte(); + assertThat(XContentType.parseMediaType("application/vnd.elasticsearch+json;compatible-with=" + version), + equalTo("application/json")); + assertThat(XContentType.parseMediaType("application/vnd.elasticsearch+cbor;compatible-with=" + version), + equalTo("application/cbor")); + assertThat(XContentType.parseMediaType("application/vnd.elasticsearch+smile;compatible-with=" + version), + equalTo("application/smile")); + assertThat(XContentType.parseMediaType("application/json"), + equalTo("application/json")); + + + assertThat(XContentType.parseMediaType("APPLICATION/VND.ELASTICSEARCH+JSON;COMPATIBLE-WITH=" + version), + equalTo("application/json")); + assertThat(XContentType.parseMediaType("APPLICATION/JSON"), + equalTo("application/json")); + } + + + public void testVersionParsing() { + String version = String.valueOf(Math.abs(randomByte())); + assertThat(XContentType.parseVersion("application/vnd.elasticsearch+json;compatible-with=" + version), + equalTo(version)); + assertThat(XContentType.parseVersion("application/vnd.elasticsearch+cbor;compatible-with=" + version), + equalTo(version)); + assertThat(XContentType.parseVersion("application/vnd.elasticsearch+smile;compatible-with=" + version), + equalTo(version)); + assertThat(XContentType.parseVersion("application/json"), + nullValue()); + + + assertThat(XContentType.parseVersion("APPLICATION/VND.ELASTICSEARCH+JSON;COMPATIBLE-WITH=" + version), + equalTo(version)); + assertThat(XContentType.parseVersion("APPLICATION/JSON"), + nullValue()); + } + + public void testVersionParsingOnText() { + String version = String.valueOf(Math.abs(randomByte())); + assertThat(XContentType.parseVersion("text/vnd.elasticsearch+csv;compatible-with=" + version), + equalTo(version)); + assertThat(XContentType.parseVersion("text/vnd.elasticsearch+text;compatible-with=" + version), + equalTo(version)); + assertThat(XContentType.parseVersion("text/vnd.elasticsearch+tab-separated-values;compatible-with=" + version), + equalTo(version)); + assertThat(XContentType.parseVersion("text/csv"), + nullValue()); + + assertThat(XContentType.parseVersion("TEXT/VND.ELASTICSEARCH+CSV;COMPATIBLE-WITH=" + version), + equalTo(version)); + assertThat(XContentType.parseVersion("TEXT/csv"), + nullValue()); + } } diff --git a/server/src/test/java/org/elasticsearch/http/DefaultRestChannelTests.java b/server/src/test/java/org/elasticsearch/http/DefaultRestChannelTests.java index 14ec4f296f447..51ad4c3e9cd5a 100644 --- a/server/src/test/java/org/elasticsearch/http/DefaultRestChannelTests.java +++ b/server/src/test/java/org/elasticsearch/http/DefaultRestChannelTests.java @@ -19,6 +19,8 @@ package org.elasticsearch.http; +import org.apache.http.HttpHeaders; +import org.elasticsearch.Version; import org.elasticsearch.action.ActionListener; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; @@ -212,6 +214,15 @@ public void testHeadersSet() { assertEquals(resp.contentType(), headers.get(DefaultRestChannel.CONTENT_TYPE).get(0)); } + public void testCompatibleParamIsSet() { + int majorVersion = Version.CURRENT.major - 1; + final TestRequest httpRequest = new TestRequest(HttpRequest.HttpVersion.HTTP_1_1, RestRequest.Method.GET, "/"); + httpRequest.getHeaders().put(HttpHeaders.ACCEPT, List.of("application/vnd.elasticsearch+json;compatible-with=" + majorVersion)); + final RestRequest request = RestRequest.request(xContentRegistry(), httpRequest, httpChannel); + + assertEquals(Version.fromString(majorVersion+".0.0"), request.getCompatibleApiVersion()); + } + public void testCookiesSet() { Settings settings = Settings.builder().put(HttpTransportSettings.SETTING_HTTP_RESET_COOKIES.getKey(), true).build(); final TestRequest httpRequest = new TestRequest(HttpRequest.HttpVersion.HTTP_1_1, RestRequest.Method.GET, "/"); @@ -404,7 +415,7 @@ private TestResponse executeRequest(final Settings settings, final String origin return responseCaptor.getValue(); } - private static class TestRequest implements HttpRequest { + public static class TestRequest implements HttpRequest { private final Supplier version; private final RestRequest.Method method; diff --git a/server/src/test/java/org/elasticsearch/rest/CompatibleHeaderCombinationTests.java b/server/src/test/java/org/elasticsearch/rest/CompatibleHeaderCombinationTests.java new file mode 100644 index 0000000000000..b43e7d10823b5 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/rest/CompatibleHeaderCombinationTests.java @@ -0,0 +1,241 @@ +/* + * 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.rest; + +import org.elasticsearch.Version; +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.hamcrest.ElasticsearchMatchers; +import org.elasticsearch.test.rest.FakeRestRequest; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; + +public class CompatibleHeaderCombinationTests extends ESTestCase { + int CURRENT_VERSION = Version.CURRENT.major; + int PREVIOUS_VERSION = Version.CURRENT.major - 1; + int OBSOLETE_VERSION = Version.CURRENT.major - 2; + + public void testAcceptAndContentTypeCombinations() { + assertThat(requestWith(acceptHeader(PREVIOUS_VERSION), contentTypeHeader(PREVIOUS_VERSION), bodyPresent()), + Matchers.allOf(requestCreated(), isCompatible())); + + assertThat(requestWith(acceptHeader(PREVIOUS_VERSION), contentTypeHeader(PREVIOUS_VERSION), bodyNotPresent()), + Matchers.allOf(requestCreated(), isCompatible())); + + expectThrows(RestRequest.CompatibleApiHeadersCombinationException.class, () -> + requestWith(acceptHeader(PREVIOUS_VERSION), contentTypeHeader(CURRENT_VERSION), bodyPresent())); + + // no body - content-type is ignored + assertThat(requestWith(acceptHeader(PREVIOUS_VERSION), contentTypeHeader(CURRENT_VERSION), bodyNotPresent()), + Matchers.allOf(requestCreated(), isCompatible())); + // no body - content-type is ignored + assertThat(requestWith(acceptHeader(CURRENT_VERSION), contentTypeHeader(PREVIOUS_VERSION), bodyNotPresent()), + Matchers.allOf(requestCreated(), not(isCompatible()))); + + expectThrows(RestRequest.CompatibleApiHeadersCombinationException.class, () -> + requestWith(acceptHeader(CURRENT_VERSION), contentTypeHeader(PREVIOUS_VERSION), bodyPresent())); + + assertThat(requestWith(acceptHeader(CURRENT_VERSION), contentTypeHeader(CURRENT_VERSION), bodyPresent()), + Matchers.allOf(requestCreated(), not(isCompatible()))); + + assertThat(requestWith(acceptHeader(CURRENT_VERSION), contentTypeHeader(CURRENT_VERSION), bodyNotPresent()), + Matchers.allOf(requestCreated(), not(isCompatible()))); + + //tests when body present and one of the headers missing - versioning is required on both when body is present + expectThrows(RestRequest.CompatibleApiHeadersCombinationException.class, () -> + requestWith(acceptHeader(PREVIOUS_VERSION), contentTypeHeader(null), bodyPresent())); + + expectThrows(RestRequest.CompatibleApiHeadersCombinationException.class, () -> + requestWith(acceptHeader(CURRENT_VERSION), contentTypeHeader(null), bodyPresent())); + + expectThrows(RestRequest.CompatibleApiHeadersCombinationException.class, () -> + requestWith(acceptHeader(null), contentTypeHeader(CURRENT_VERSION), bodyPresent())); + + expectThrows(RestRequest.CompatibleApiHeadersCombinationException.class, () -> + requestWith(acceptHeader(null), contentTypeHeader(PREVIOUS_VERSION), bodyPresent())); + + //tests when body NOT present and one of the headers missing + assertThat(requestWith(acceptHeader(PREVIOUS_VERSION), contentTypeHeader(null), bodyNotPresent()), + Matchers.allOf(requestCreated(), isCompatible())); + + assertThat(requestWith(acceptHeader(CURRENT_VERSION), contentTypeHeader(null), bodyNotPresent()), + Matchers.allOf(requestCreated(), not(isCompatible()))); + + //body not present - accept header is missing - it will default to Current version. Version on content type is ignored + assertThat(requestWith(acceptHeader(null), contentTypeHeader(PREVIOUS_VERSION), bodyNotPresent()), + Matchers.allOf(requestCreated(), not(isCompatible()))); + + assertThat(requestWith(acceptHeader(null), contentTypeHeader(CURRENT_VERSION), bodyNotPresent()), + Matchers.allOf(requestCreated(), not(isCompatible()))); + + assertThat(requestWith(acceptHeader(null), contentTypeHeader(null), bodyNotPresent()), + Matchers.allOf(requestCreated(), not(isCompatible()))); + + //Accept header = application/json means current version. If body is provided then accept and content-Type should be the same + assertThat(requestWith(acceptHeader("application/json"), contentTypeHeader(null), bodyNotPresent()), + Matchers.allOf(requestCreated(), not(isCompatible()))); + + assertThat(requestWith(acceptHeader("application/json"), contentTypeHeader("application/json"), bodyPresent()), + Matchers.allOf(requestCreated(), not(isCompatible()))); + + assertThat(requestWith(acceptHeader(null), contentTypeHeader("application/json"), bodyPresent()), + Matchers.allOf(requestCreated(), not(isCompatible()))); + } + + public void testObsoleteVersion() { + expectThrows(RestRequest.CompatibleApiHeadersCombinationException.class, () -> + requestWith(acceptHeader(OBSOLETE_VERSION), contentTypeHeader(OBSOLETE_VERSION), bodyPresent())); + + expectThrows(RestRequest.CompatibleApiHeadersCombinationException.class, () -> + requestWith(acceptHeader(OBSOLETE_VERSION), contentTypeHeader(null), bodyNotPresent())); + } + + + public void testMediaTypeCombinations() { + //body not present - ignore content-type + assertThat(requestWith(acceptHeader(null), contentTypeHeader(PREVIOUS_VERSION), bodyNotPresent()), + Matchers.allOf(requestCreated(), not(isCompatible()))); + + assertThat(requestWith(acceptHeader(null), contentTypeHeader("application/json"), bodyNotPresent()), + Matchers.allOf(requestCreated(), not(isCompatible()))); + + assertThat(requestWith(acceptHeader("*/*"), contentTypeHeader("application/json"), bodyNotPresent()), + Matchers.allOf(requestCreated(), not(isCompatible()))); + + //this is for instance used by SQL + assertThat(requestWith(acceptHeader("application/json"), contentTypeHeader("application/cbor"), bodyPresent()), + Matchers.allOf(requestCreated(), not(isCompatible()))); + + assertThat(requestWith(acceptHeader("application/vnd.elasticsearch+json;compatible-with=7"), + contentTypeHeader("application/vnd.elasticsearch+cbor;compatible-with=7"), bodyPresent()), + Matchers.allOf(requestCreated(), isCompatible())); + + //different versions on different media types + expectThrows(RestRequest.CompatibleApiHeadersCombinationException.class, () -> + requestWith(acceptHeader("application/vnd.elasticsearch+json;compatible-with=7"), + contentTypeHeader("application/vnd.elasticsearch+cbor;compatible-with=8"), bodyPresent())); + } + + public void testTextMediaTypes() { + assertThat(requestWith(acceptHeader("text/tab-separated-values"), contentTypeHeader("application/json"), bodyNotPresent()), + Matchers.allOf(requestCreated(), not(isCompatible()))); + + assertThat(requestWith(acceptHeader("text/plain"), contentTypeHeader("application/json"), bodyNotPresent()), + Matchers.allOf(requestCreated(), not(isCompatible()))); + + assertThat(requestWith(acceptHeader("text/csv"), contentTypeHeader("application/json"), bodyNotPresent()), + Matchers.allOf(requestCreated(), not(isCompatible()))); + + //versioned + assertThat(requestWith(acceptHeader("text/vnd.elasticsearch+tab-separated-values;compatible-with=7"), + contentTypeHeader(7), bodyNotPresent()), + Matchers.allOf(requestCreated(), isCompatible())); + + assertThat(requestWith(acceptHeader("text/vnd.elasticsearch+plain;compatible-with=7"), + contentTypeHeader(7), bodyNotPresent()), + Matchers.allOf(requestCreated(), isCompatible())); + + assertThat(requestWith(acceptHeader("text/vnd.elasticsearch+csv;compatible-with=7"), + contentTypeHeader(7), bodyNotPresent()), + Matchers.allOf(requestCreated(), isCompatible())); + } + + private Matcher requestCreated() { + return Matchers.not(nullValue(RestRequest.class)); + } + + private Matcher isCompatible() { + return requestHasVersion(Version.CURRENT.major - 1); + } + + private Matcher requestHasVersion(int version) { + return ElasticsearchMatchers.HasPropertyLambdaMatcher.hasProperty(restRequest -> + restRequest.getCompatibleApiVersion(), equalTo(Version.fromString(version + ".0.0"))); + } + + private String bodyNotPresent() { + return null; + } + + private String bodyPresent() { + return "some body"; + } + + private List contentTypeHeader(int version) { + return mediaType(version); + } + + private List acceptHeader(int version) { + return mediaType(version); + } + + private List acceptHeader(String value) { + return headerValue(value); + } + + private List contentTypeHeader(String value) { + return headerValue(value); + } + + private List headerValue(String value) { + if (value != null) { + return List.of(value); + } + return null; + } + + private List mediaType(Integer version) { + if (version != null) { + return List.of("application/vnd.elasticsearch+json;compatible-with=" + version); + } + return null; + } + + private RestRequest requestWith(List accept, List contentType, String body) { + FakeRestRequest.Builder builder = new FakeRestRequest.Builder(NamedXContentRegistry.EMPTY); + + builder.withHeaders(createHeaders(accept, contentType)); + if (body != null) { + // xContentType header is set explicitly in headers + builder.withContent(new BytesArray(body), null); + } + return builder.build(); + } + + private Map> createHeaders(List accept, List contentType) { + Map> headers = new HashMap<>(); + if (accept != null) { + headers.put("Accept", accept); + } + if (contentType != null) { + headers.put("Content-Type", contentType); + } + return headers; + } +} diff --git a/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java b/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java index 657cce945c947..9848ce0d83392 100644 --- a/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java +++ b/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java @@ -19,6 +19,7 @@ package org.elasticsearch.rest; +import org.elasticsearch.Version; import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.common.breaker.CircuitBreaker; import org.elasticsearch.common.bytes.BytesArray; @@ -44,6 +45,7 @@ import org.elasticsearch.test.rest.FakeRestRequest; import org.elasticsearch.usage.UsageService; import org.junit.Before; +import org.mockito.Mockito; import java.io.IOException; import java.util.Arrays; @@ -57,6 +59,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; @@ -127,7 +130,7 @@ public MethodHandlers next() { assertEquals("true", threadContext.getHeader("header.1")); assertEquals("true", threadContext.getHeader("header.2")); assertNull(threadContext.getHeader("header.3")); - }, RestRequest.Method.GET); + }, Version.CURRENT, RestRequest.Method.GET); } }); AssertingChannel channel = new AssertingChannel(fakeRequest, false, RestStatus.BAD_REQUEST); @@ -178,7 +181,7 @@ public void testRegisterAsDeprecatedHandler() { RestRequest.Method method = randomFrom(RestRequest.Method.values()); String path = "/_" + randomAlphaOfLengthBetween(1, 6); - RestHandler handler = mock(RestHandler.class); + RestHandler handler = v8mockHandler(); String deprecationMessage = randomAlphaOfLengthBetween(1, 10); // don't want to test everything -- just that it actually wraps the handler @@ -194,7 +197,7 @@ public void testRegisterWithDeprecatedHandler() { final RestRequest.Method method = randomFrom(RestRequest.Method.values()); final String path = "/_" + randomAlphaOfLengthBetween(1, 6); - final RestHandler handler = mock(RestHandler.class); + final RestHandler handler = v8mockHandler(); final RestRequest.Method deprecatedMethod = randomFrom(RestRequest.Method.values()); final String deprecatedPath = "/_" + randomAlphaOfLengthBetween(1, 6); @@ -219,7 +222,7 @@ public void testRegisterSecondMethodWithDifferentNamedWildcard() { final String path = "/_" + randomAlphaOfLengthBetween(1, 6); - RestHandler handler = mock(RestHandler.class); + RestHandler handler = v8mockHandler(); restController.registerHandler(firstMethod, path + "/{wildcard1}", handler); IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, @@ -228,6 +231,12 @@ public void testRegisterSecondMethodWithDifferentNamedWildcard() { assertThat(exception.getMessage(), equalTo("Trying to use conflicting wildcard names for same path: wildcard1 and wildcard2")); } + private RestHandler v8mockHandler() { + RestHandler mock = mock(RestHandler.class); + Mockito.when(mock.compatibleWithVersion()).thenReturn(Version.CURRENT); + return mock; + } + public void testRestHandlerWrapper() throws Exception { AtomicBoolean handlerCalled = new AtomicBoolean(false); AtomicBoolean wrapperCalled = new AtomicBoolean(false); @@ -610,6 +619,60 @@ public Exception getInboundException() { } + public void testDispatchCompatibleHandler() { + final byte version = (byte) (Version.CURRENT.major - 1); + + final String mimeType = randomCompatibleMimeType(version); + String content = randomAlphaOfLength((int) Math.round(BREAKER_LIMIT.getBytes() / inFlightRequestsBreaker.getOverhead())); + final List mimeTypeList = Collections.singletonList(mimeType); + FakeRestRequest fakeRestRequest = new FakeRestRequest.Builder(NamedXContentRegistry.EMPTY) + .withContent(new BytesArray(content), RestRequest.parseContentType(mimeTypeList)) + .withPath("/foo") + .withHeaders(Map.of("Content-Type", mimeTypeList, "Accept", mimeTypeList)) + .build(); + AssertingChannel channel = new AssertingChannel(fakeRestRequest, true, RestStatus.OK); + restController.registerHandler(RestRequest.Method.GET, "/foo", new RestHandler() { + @Override + public void handleRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception { + XContentBuilder xContentBuilder = channel.newBuilder(); + assertThat(xContentBuilder.getCompatibleMajorVersion(), equalTo(version)); + channel.sendResponse(new BytesRestResponse(RestStatus.OK, BytesRestResponse.TEXT_CONTENT_TYPE, BytesArray.EMPTY)); + } + + @Override + public Version compatibleWithVersion() { + return Version.V_7_0_0; + } + }); + + assertFalse(channel.getSendResponseCalled()); + restController.dispatchRequest(fakeRestRequest, channel, new ThreadContext(Settings.EMPTY)); + assertTrue(channel.getSendResponseCalled()); + } + + public void testRegisterIncompatibleVersionHandler() { + final byte version = (byte) (Version.CURRENT.major - 2); + + expectThrows(AssertionError.class, + () -> restController.registerHandler(RestRequest.Method.GET, "/foo", new RestHandler() { + @Override + public void handleRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception { + } + + @Override + public Version compatibleWithVersion() { + return Version.fromString(version + ".0.0"); + } + })); + } + + private String randomCompatibleMimeType(byte version) { + String subtype = randomFrom(Stream.of(XContentType.values()) + .map(XContentType::shortName) + .toArray(String[]::new)); + return randomFrom("application/vnd.elasticsearch+" + subtype + ";compatible-with=" + version); + } + private static final class TestHttpServerTransport extends AbstractLifecycleComponent implements HttpServerTransport { diff --git a/test/framework/src/main/java/org/elasticsearch/test/rest/FakeRestRequest.java b/test/framework/src/main/java/org/elasticsearch/test/rest/FakeRestRequest.java index e36d4ae13b668..62381877164d4 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/rest/FakeRestRequest.java +++ b/test/framework/src/main/java/org/elasticsearch/test/rest/FakeRestRequest.java @@ -196,8 +196,13 @@ public Builder(NamedXContentRegistry xContentRegistry) { this.xContentRegistry = xContentRegistry; } + public Builder addHeaders(Map> headers) { + this.headers.putAll(headers); + return this; + } + public Builder withHeaders(Map> headers) { - this.headers = headers; + this.headers.putAll(headers); return this; } @@ -238,6 +243,19 @@ public FakeRestRequest build() { FakeHttpRequest fakeHttpRequest = new FakeHttpRequest(method, path, content, headers, inboundException); return new FakeRestRequest(xContentRegistry, fakeHttpRequest, params, new FakeHttpChannel(address)); } + + @Override + public String toString() { + return "Builder{" + + "xContentRegistry=" + xContentRegistry + + ", headers=" + headers + + ", params=" + params + + ", content=" + content + + ", path='" + path + '\'' + + ", method=" + method + + ", address=" + address + + '}'; + } } public static String requestToString(RestRequest restRequest) { diff --git a/test/framework/src/main/java/org/elasticsearch/test/rest/RestActionTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/rest/RestActionTestCase.java index a5d932a3d1a3d..5aea384de6a56 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/rest/RestActionTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/rest/RestActionTestCase.java @@ -38,7 +38,7 @@ * that can be used to register individual REST actions, and test request handling. */ public abstract class RestActionTestCase extends ESTestCase { - private RestController controller; + protected RestController controller; protected NodeClient nodeClient; @Before diff --git a/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/ClientYamlTestCandidate.java b/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/ClientYamlTestCandidate.java index dd650a38ebbd7..07ddecca58361 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/ClientYamlTestCandidate.java +++ b/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/ClientYamlTestCandidate.java @@ -18,10 +18,12 @@ */ package org.elasticsearch.test.rest.yaml; +import org.elasticsearch.test.rest.yaml.section.ClientYamlTestSection; import org.elasticsearch.test.rest.yaml.section.ClientYamlTestSuite; import org.elasticsearch.test.rest.yaml.section.SetupSection; import org.elasticsearch.test.rest.yaml.section.TeardownSection; -import org.elasticsearch.test.rest.yaml.section.ClientYamlTestSection; + +import java.util.Objects; /** * Wraps {@link ClientYamlTestSection}s ready to be run. Each test section is associated to its {@link ClientYamlTestSuite}. @@ -68,4 +70,17 @@ public ClientYamlTestSection getTestSection() { public String toString() { return getTestPath(); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ClientYamlTestCandidate that = (ClientYamlTestCandidate) o; + return Objects.equals(getTestPath(), that.getTestPath()); + } + + @Override + public int hashCode() { + return Objects.hash(getTestPath()); + } } diff --git a/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/ClientYamlTestClient.java b/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/ClientYamlTestClient.java index 64b61773461f6..0040116ff4fce 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/ClientYamlTestClient.java +++ b/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/ClientYamlTestClient.java @@ -23,6 +23,7 @@ import org.apache.http.HttpHost; import org.apache.http.client.methods.HttpGet; import org.apache.http.entity.ContentType; +import org.apache.http.protocol.HTTP; import org.apache.http.util.EntityUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -179,6 +180,11 @@ public ClientYamlTestResponse callApi(String apiName, Map params for (Map.Entry param : queryStringParams.entrySet()) { request.addParameter(param.getKey(), param.getValue()); } + // that means content type was set based on Content-Type from entity. + // Setting content-type on both entity and headers will result in exception in server + if (entity != null && entity.getContentType() != null) { + headers.remove(HTTP.CONTENT_TYPE); + } request.setEntity(entity); setOptions(request, headers); diff --git a/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/ClientYamlTestExecutionContext.java b/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/ClientYamlTestExecutionContext.java index b1337172a5679..de36c25f2998b 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/ClientYamlTestExecutionContext.java +++ b/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/ClientYamlTestExecutionContext.java @@ -23,6 +23,7 @@ import org.apache.http.HttpEntity; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.ContentType; +import org.apache.http.protocol.HTTP; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.lucene.util.BytesRef; @@ -118,8 +119,10 @@ private HttpEntity createEntity(List> bodies, Map bytesRefList = new ArrayList<>(bodies.size()); @@ -137,7 +140,16 @@ private HttpEntity createEntity(List> bodies, Map headers, ByteArrayEntity byteArrayEntity) { + if (byteArrayEntity.getContentType() != null && headers.get(HTTP.CONTENT_TYPE) != null) { + byteArrayEntity.setContentType(headers.get(HTTP.CONTENT_TYPE)); } } diff --git a/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/ESClientYamlSuiteTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/ESClientYamlSuiteTestCase.java index 7dc3a4161104c..5e97a40af3405 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/ESClientYamlSuiteTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/ESClientYamlSuiteTestCase.java @@ -89,7 +89,7 @@ public abstract class ESClientYamlSuiteTestCase extends ESRestTestCase { */ private static final String REST_TESTS_VALIDATE_SPEC = "tests.rest.validate_spec"; - private static final String TESTS_PATH = "/rest-api-spec/test"; + protected static final String TESTS_PATH = "/rest-api-spec/test"; private static final String SPEC_PATH = "/rest-api-spec/api"; /** @@ -183,15 +183,19 @@ public static void closeClient() throws IOException { * defined in {@link ExecutableSection}. */ public static Iterable createParameters() throws Exception { - return createParameters(ExecutableSection.XCONTENT_REGISTRY); + return createParameters(ExecutableSection.XCONTENT_REGISTRY, TESTS_PATH); } - /** - * Create parameters for this parameterized test. - */ - public static Iterable createParameters(NamedXContentRegistry executeableSectionRegistry) throws Exception { + public static Iterable createParameters(NamedXContentRegistry registry) throws Exception { + return createParameters(registry, TESTS_PATH); + } + + /** + * Create parameters for this parameterized test. + */ + public static Iterable createParameters(NamedXContentRegistry executeableSectionRegistry, String testsPath) throws Exception { String[] paths = resolvePathsProperty(REST_TESTS_SUITE, ""); // default to all tests under the test root - Map> yamlSuites = loadSuites(paths); + Map> yamlSuites = loadSuites(testsPath, paths); List suites = new ArrayList<>(); IllegalArgumentException validationException = null; // yaml suites are grouped by directory (effectively by api) @@ -236,9 +240,9 @@ public static Iterable createParameters(NamedXContentRegistry executea /** Find all yaml suites that match the given list of paths from the root test path. */ // pkg private for tests - static Map> loadSuites(String... paths) throws Exception { + static Map> loadSuites(String testsPath, String... paths) throws Exception { Map> files = new HashMap<>(); - Path root = PathUtils.get(ESClientYamlSuiteTestCase.class.getResource(TESTS_PATH).toURI()); + Path root = PathUtils.get(ESClientYamlSuiteTestCase.class.getResource(testsPath).toURI()); for (String strPath : paths) { Path path = root.resolve(strPath); if (Files.isDirectory(path)) { @@ -446,6 +450,6 @@ protected final boolean preserveDataStreamsUponCompletion() { // The client runners need to be adjust to remove data streams after each test too, // otherwise rest yaml tests using data streams succeed in Elasticsearch, but may fail when clients run // the yaml test suite. In the mean time we should delete data streams manually after each test. - return true; + return false; //TODO PG temporary fix to allow compat testing } } diff --git a/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/section/DoSection.java b/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/section/DoSection.java index 78aff9e542d6e..05b86800d24e2 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/section/DoSection.java +++ b/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/section/DoSection.java @@ -207,6 +207,13 @@ public static DoSection parse(XContentParser parser) throws IOException { private ApiCallSection apiCallSection; private List expectedWarningHeaders = emptyList(); private List allowedWarningHeaders = emptyList(); + //TODO: be more selective in what is ignored + private boolean ignoreWarnings = false; + + public void setIgnoreWarnings(boolean ignoreWarnings) { + this.ignoreWarnings = ignoreWarnings; + } + public DoSection(XContentLocation location) { this.location = location; @@ -316,6 +323,9 @@ public void execute(ClientYamlTestExecutionContext executionContext) throws IOEx * Check that the response contains only the warning headers that we expect. */ void checkWarningHeaders(final List warningHeaders, final Version masterVersion) { + if (ignoreWarnings) { + return; + } final List unexpected = new ArrayList<>(); final List unmatched = new ArrayList<>(); final List missing = new ArrayList<>(); @@ -488,6 +498,7 @@ public String toString() { }; } + /** * Selector that composes two selectors, running the "right" most selector * first and then running the "left" selector on the results of the "right" diff --git a/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/section/MatchAssertion.java b/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/section/MatchAssertion.java index 211fa2f20959e..b74cfd01007ef 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/section/MatchAssertion.java +++ b/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/section/MatchAssertion.java @@ -56,6 +56,12 @@ public MatchAssertion(XContentLocation location, String field, Object expectedVa @Override protected void doAssert(Object actualValue, Object expectedValue) { + // TODO this needs to be moved to override directory + if(getField().equals("_type") ){ + assertThat(actualValue, equalTo("_doc")); + return; + } + //if the value is wrapped into / it is a regexp (e.g. /s+d+/) if (expectedValue instanceof String) { String expValue = ((String) expectedValue).trim(); diff --git a/test/framework/src/test/java/org/elasticsearch/test/rest/yaml/ESClientYamlSuiteTestCaseTests.java b/test/framework/src/test/java/org/elasticsearch/test/rest/yaml/ESClientYamlSuiteTestCaseTests.java index ae64dbc893d81..69a6de0e3783b 100644 --- a/test/framework/src/test/java/org/elasticsearch/test/rest/yaml/ESClientYamlSuiteTestCaseTests.java +++ b/test/framework/src/test/java/org/elasticsearch/test/rest/yaml/ESClientYamlSuiteTestCaseTests.java @@ -25,6 +25,7 @@ import org.elasticsearch.test.ESTestCase; +import static org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase.TESTS_PATH; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.Matchers.greaterThan; @@ -32,29 +33,29 @@ public class ESClientYamlSuiteTestCaseTests extends ESTestCase { public void testLoadAllYamlSuites() throws Exception { - Map> yamlSuites = ESClientYamlSuiteTestCase.loadSuites(""); + Map> yamlSuites = ESClientYamlSuiteTestCase.loadSuites(TESTS_PATH, ""); assertEquals(2, yamlSuites.size()); } public void testLoadSingleYamlSuite() throws Exception { - Map> yamlSuites = ESClientYamlSuiteTestCase.loadSuites("suite1/10_basic"); + Map> yamlSuites = ESClientYamlSuiteTestCase.loadSuites(TESTS_PATH, "suite1/10_basic"); assertSingleFile(yamlSuites, "suite1", "10_basic.yml"); //extension .yaml is optional - yamlSuites = ESClientYamlSuiteTestCase.loadSuites("suite1/10_basic"); + yamlSuites = ESClientYamlSuiteTestCase.loadSuites(TESTS_PATH, "suite1/10_basic"); assertSingleFile(yamlSuites, "suite1", "10_basic.yml"); } public void testLoadMultipleYamlSuites() throws Exception { //single directory - Map> yamlSuites = ESClientYamlSuiteTestCase.loadSuites("suite1"); + Map> yamlSuites = ESClientYamlSuiteTestCase.loadSuites(TESTS_PATH, "suite1"); assertThat(yamlSuites, notNullValue()); assertThat(yamlSuites.size(), equalTo(1)); assertThat(yamlSuites.containsKey("suite1"), equalTo(true)); assertThat(yamlSuites.get("suite1").size(), greaterThan(1)); //multiple directories - yamlSuites = ESClientYamlSuiteTestCase.loadSuites("suite1", "suite2"); + yamlSuites = ESClientYamlSuiteTestCase.loadSuites(TESTS_PATH, "suite1", "suite2"); assertThat(yamlSuites, notNullValue()); assertThat(yamlSuites.size(), equalTo(2)); assertThat(yamlSuites.containsKey("suite1"), equalTo(true)); @@ -63,7 +64,7 @@ public void testLoadMultipleYamlSuites() throws Exception { assertEquals(2, yamlSuites.get("suite2").size()); //multiple paths, which can be both directories or yaml test suites (with optional file extension) - yamlSuites = ESClientYamlSuiteTestCase.loadSuites("suite2/10_basic", "suite1"); + yamlSuites = ESClientYamlSuiteTestCase.loadSuites(TESTS_PATH, "suite2/10_basic", "suite1"); assertThat(yamlSuites, notNullValue()); assertThat(yamlSuites.size(), equalTo(2)); assertThat(yamlSuites.containsKey("suite2"), equalTo(true)); diff --git a/test/framework/src/test/java/org/elasticsearch/test/rest/yaml/section/MatchAssertionTests.java b/test/framework/src/test/java/org/elasticsearch/test/rest/yaml/section/MatchAssertionTests.java index ddcffa7ac5adc..5d751702d5516 100644 --- a/test/framework/src/test/java/org/elasticsearch/test/rest/yaml/section/MatchAssertionTests.java +++ b/test/framework/src/test/java/org/elasticsearch/test/rest/yaml/section/MatchAssertionTests.java @@ -52,4 +52,14 @@ public void testNullInMap() { matchAssertion.doAssert(emptyMap(), matchAssertion.getExpectedValue())); assertThat(e.getMessage(), containsString("expected [null] but not found")); } + + public void testNullInMap2() { + XContentLocation xContentLocation = new XContentLocation(0, 0); + MatchAssertion matchAssertion = + new MatchAssertion(xContentLocation, "test_index.mappings.test_type.text.mapping.text.type", "type"); + matchAssertion.doAssert(singletonMap("a", null), matchAssertion.getExpectedValue()); + AssertionError e = expectThrows(AssertionError.class, () -> + matchAssertion.doAssert(emptyMap(), matchAssertion.getExpectedValue())); + assertThat(e.getMessage(), containsString("expected [null] but not found")); + } } diff --git a/x-pack/plugin/rest-compat/build.gradle b/x-pack/plugin/rest-compat/build.gradle new file mode 100644 index 0000000000000..59b4ddb0f32e9 --- /dev/null +++ b/x-pack/plugin/rest-compat/build.gradle @@ -0,0 +1,18 @@ +evaluationDependsOn(xpackModule('core')) + +apply plugin: 'elasticsearch.esplugin' +apply plugin: 'elasticsearch.rest-resources' + +esplugin { + name 'rest-compat' + description 'A plugin for Rest compat request features' + classname 'org.elasticsearch.compat.RestCompatRequestPlugin' + extendedPlugins = ['x-pack-core'] +} + +dependencies { + compileOnly project(path: xpackModule('core'), configuration: 'default') + testImplementation project(path: xpackModule('core'), configuration: 'testArtifacts') +} + + diff --git a/x-pack/plugin/rest-compat/src/main/java/org/elasticsearch/compat/CompatRequestWrapper.java b/x-pack/plugin/rest-compat/src/main/java/org/elasticsearch/compat/CompatRequestWrapper.java new file mode 100644 index 0000000000000..4e85e0e3444e3 --- /dev/null +++ b/x-pack/plugin/rest-compat/src/main/java/org/elasticsearch/compat/CompatRequestWrapper.java @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +package org.elasticsearch.compat; + +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.rest.RestChannel; +import org.elasticsearch.rest.RestHandler; +import org.elasticsearch.rest.RestRequest; + +public class CompatRequestWrapper implements RestHandler { + @Override + public void handleRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception { + System.out.println("heee"); + } +} diff --git a/x-pack/plugin/rest-compat/src/main/java/org/elasticsearch/compat/RestCompatRequestPlugin.java b/x-pack/plugin/rest-compat/src/main/java/org/elasticsearch/compat/RestCompatRequestPlugin.java new file mode 100644 index 0000000000000..c8f6c1a856b0a --- /dev/null +++ b/x-pack/plugin/rest-compat/src/main/java/org/elasticsearch/compat/RestCompatRequestPlugin.java @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +package org.elasticsearch.compat; + +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.plugins.ActionPlugin; +import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.rest.RestHandler; +import org.elasticsearch.rest.RestHeaderDefinition; +import org.elasticsearch.xpack.core.security.SecuritySettings; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.function.UnaryOperator; + +public class RestCompatRequestPlugin extends Plugin implements ActionPlugin { + static final Setting SIMPLE_SETTING = Setting.simpleString("compat.setting", Setting.Property.NodeScope); + @Override + public List> getSettings() { + return Arrays.asList(SIMPLE_SETTING); + } + + @Override + public Settings additionalSettings() { + final Settings.Builder builder = Settings.builder(); + builder.put("compat.setting", true); + return builder.build(); + } + +} diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/SecurityRestFilter.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/SecurityRestFilter.java index d2327dcb3e833..9ee1ffd2d9c01 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/SecurityRestFilter.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/SecurityRestFilter.java @@ -10,6 +10,7 @@ import org.apache.logging.log4j.message.ParameterizedMessage; import org.apache.logging.log4j.util.Supplier; import org.elasticsearch.ExceptionsHelper; +import org.elasticsearch.Version; import org.elasticsearch.action.ActionListener; import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.common.util.concurrent.ThreadContext; @@ -135,4 +136,9 @@ private RestRequest maybeWrapRestRequest(RestRequest restRequest) throws IOExcep } return restRequest; } + + @Override + public Version compatibleWithVersion() { + return restHandler.compatibleWithVersion(); + } } diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/TextFormat.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/TextFormat.java index dd837e65315aa..bde664f4ad4af 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/TextFormat.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/TextFormat.java @@ -258,6 +258,7 @@ boolean hasHeader(RestRequest request) { } static TextFormat fromMediaTypeOrFormat(String accept) { + //TODO we should include version parsing here too. This and XContentType should be unified somehow.. for (TextFormat text : values()) { String contentType = text.contentType(); if (contentType.equalsIgnoreCase(accept)