From 28ffe75ddd1c7fe88a364fa3215572c7bfdad6f4 Mon Sep 17 00:00:00 2001 From: Jake Landis Date: Tue, 3 Mar 2020 17:27:48 -0600 Subject: [PATCH 01/16] minimal and temporary testing for rest api compatibility --- qa/rest-compat-tests/build.gradle | 22 ++++++ .../rest/compat/AbstractCompatRestTest.java | 75 +++++++++++++++++++ .../rest/compat/CompatRestIT.java | 15 ++++ .../rest/yaml/ClientYamlTestCandidate.java | 17 ++++- .../rest/yaml/ESClientYamlSuiteTestCase.java | 10 +-- .../test/rest/yaml/section/DoSection.java | 11 +++ 6 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 qa/rest-compat-tests/build.gradle create mode 100644 qa/rest-compat-tests/src/main/java/org/elasticsearch/rest/compat/AbstractCompatRestTest.java create mode 100644 qa/rest-compat-tests/src/test/java/org/elasticsearch/rest/compat/CompatRestIT.java diff --git a/qa/rest-compat-tests/build.gradle b/qa/rest-compat-tests/build.gradle new file mode 100644 index 0000000000000..ce160b6127582 --- /dev/null +++ b/qa/rest-compat-tests/build.gradle @@ -0,0 +1,22 @@ +/** + * 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' +} + +integTest.dependsOn(copyRestTestsResources) + +dependencies { + compile project(':test:framework') +} 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..e465224c22e26 --- /dev/null +++ b/qa/rest-compat-tests/src/main/java/org/elasticsearch/rest/compat/AbstractCompatRestTest.java @@ -0,0 +1,75 @@ +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.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.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +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.getTestSection().getExecutableSections().stream().filter(s -> s instanceof DoSection).forEach(ds -> { + DoSection doSection = (DoSection) ds; + //TODO: be more selective here + doSection.setIgnoreWarnings(true); + //TODO: use the real header compatibility header + doSection.getApiCallSection().addHeaders(Collections.singletonMap("compatible-with", "v7")); + }); + } + + + 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..7c94c62f3200d --- /dev/null +++ b/qa/rest-compat-tests/src/test/java/org/elasticsearch/rest/compat/CompatRestIT.java @@ -0,0 +1,15 @@ +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/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/ESClientYamlSuiteTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/ESClientYamlSuiteTestCase.java index 1d0104e559889..e919c00cf4d0a 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 @@ -182,15 +182,15 @@ 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 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) @@ -235,9 +235,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)) { 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 1b588f554fa53..26df12f4d67a8 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 @@ -188,6 +188,13 @@ public static DoSection parse(XContentParser parser) throws IOException { private String catchParam; private ApiCallSection apiCallSection; private List expectedWarningHeaders = 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; @@ -281,6 +288,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<>(); @@ -447,6 +457,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" From f539504b67c387521d665c7f2513d7747e85a9d1 Mon Sep 17 00:00:00 2001 From: Jake Landis Date: Wed, 4 Mar 2020 10:22:55 -0600 Subject: [PATCH 02/16] fix NPE and add compat test to temporary fixture --- .../rest-api-spec/test-compat/testme/10_testme.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 qa/rest-compat-tests/src/test/resources/rest-api-spec/test-compat/testme/10_testme.yml 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} + From e5de47b3ca03c501409bff0ccab39d6f4482f6b2 Mon Sep 17 00:00:00 2001 From: Jake Landis Date: Tue, 10 Mar 2020 09:54:52 -0500 Subject: [PATCH 03/16] Fix issues which keep the temporary compat test fixutre (#53301) --- .../smoketest/DocsClientYamlTestSuiteIT.java | 2 +- qa/rest-compat-tests/build.gradle | 2 ++ .../rest/compat/AbstractCompatRestTest.java | 18 ++++++++++++++++++ .../rest/compat/CompatRestIT.java | 18 ++++++++++++++++++ .../rest/yaml/ESClientYamlSuiteTestCase.java | 2 +- .../yaml/ESClientYamlSuiteTestCaseTests.java | 13 +++++++------ 6 files changed, 47 insertions(+), 8 deletions(-) diff --git a/docs/src/test/java/org/elasticsearch/smoketest/DocsClientYamlTestSuiteIT.java b/docs/src/test/java/org/elasticsearch/smoketest/DocsClientYamlTestSuiteIT.java index 249101cfc54a3..5d6cc8113717d 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/qa/rest-compat-tests/build.gradle b/qa/rest-compat-tests/build.gradle index ce160b6127582..56f48f7d128fc 100644 --- a/qa/rest-compat-tests/build.gradle +++ b/qa/rest-compat-tests/build.gradle @@ -20,3 +20,5 @@ integTest.dependsOn(copyRestTestsResources) dependencies { compile 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 index e465224c22e26..461f6fcd370b0 100644 --- 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 @@ -1,3 +1,21 @@ +/* + * 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; 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 index 7c94c62f3200d..0aa4201778aa8 100644 --- 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 @@ -1,3 +1,21 @@ +/* + * 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; 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 e919c00cf4d0a..2b90dcb26887a 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 @@ -88,7 +88,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"; /** 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)); From 878ed120148968965b87b9ca2ba4e31a5bc9fe81 Mon Sep 17 00:00:00 2001 From: Przemyslaw Gomulka Date: Wed, 25 Mar 2020 14:19:56 +0100 Subject: [PATCH 04/16] Compatible version of typed Index And Get API (#53228) Providing a REST infrastructure to handle compatible APIs. Rest APIs that were removed in 8 will be available only if an HTTP request had a compatible header. At the moment is is expecting an Accept header to contain application/vnd.elasticsearch+json;compatible-with=7. This might be changed though when #52370 is resolved. The REST changes contain enriching RestRequest with a parameter Compatible-With. The value is taken from Accept header. See RestRequest creation. Used when deciding if a request to a compatible handler can be dispatched. See RestController. //TODO this at the moment can be simplified to only use a value from a header. However in rest layer we often have only access to Params, so headers won't be accessible when implementing some compatible code enriching XContentBuilder with a compatible version value. See AbstractRestChannel. Used when shape of a response is changed. see DocWriteResponse and GetResult MediaType parsing change - see XContentType. because we expect a different media type now application/vnd.elasticsearch+json;compatible-with=7 (when previously application/json or similar) testing changes - compat tests will have a compatible header set. see AbstractCompatRestTest.java Compatible rest handlers - based on RestIndexAction and RestGetAction. See modules/rest-compatibility. These extend already existing handlers and register them in a RestCompatPlugin under a typed paths. They will only be accessible when a compatible header is present (use of compatibilityRequired method from RestHandler interface) Without this PR 283 tests from 7.x are failing. - rest api spec tests only with this PR 228 tests are passing - with almost all tests from index/* package passed (missing 2 tests that require change for include_type_param, to be added in a new PR) relates #51816 --- build.gradle | 1 + .../common/xcontent/XContentBuilder.java | 12 ++ .../common/xcontent/XContentType.java | 31 ++++- modules/rest-compatibility/build.gradle | 26 ++++ .../rest/compat/RestCompatPlugin.java | 62 ++++++++++ .../rest/compat/version7/RestGetActionV7.java | 59 +++++++++ .../compat/version7/RestIndexActionV7.java | 115 ++++++++++++++++++ .../action/DocWriteResponseV7Tests.java | 69 +++++++++++ .../compat/version7/RestGetActionV7Tests.java | 56 +++++++++ .../version7/RestIndexActionV7Tests.java | 76 ++++++++++++ .../rest/compat/AbstractCompatRestTest.java | 16 ++- .../action/DocWriteResponse.java | 5 + .../elasticsearch/index/get/GetResult.java | 7 ++ .../rest/AbstractRestChannel.java | 2 + .../rest/CompatibleConstants.java | 33 +++++ .../elasticsearch/rest/RestController.java | 16 ++- .../org/elasticsearch/rest/RestHandler.java | 4 + .../org/elasticsearch/rest/RestRequest.java | 23 +++- .../rest/action/document/RestGetAction.java | 2 - .../rest/action/document/RestIndexAction.java | 5 +- .../common/xcontent/XContentTypeTests.java | 37 ++++++ .../http/DefaultRestChannelTests.java | 14 ++- .../rest/RestControllerTests.java | 36 ++++++ .../rest/yaml/ESClientYamlSuiteTestCase.java | 10 +- .../rest/yaml/section/MatchAssertion.java | 6 + .../security/rest/SecurityRestFilter.java | 5 + 26 files changed, 713 insertions(+), 15 deletions(-) create mode 100644 modules/rest-compatibility/build.gradle create mode 100644 modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/RestCompatPlugin.java create mode 100644 modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.java create mode 100644 modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java create mode 100644 modules/rest-compatibility/src/test/java/org/elasticsearch/action/DocWriteResponseV7Tests.java create mode 100644 modules/rest-compatibility/src/test/java/org/elasticsearch/rest/compat/version7/RestGetActionV7Tests.java create mode 100644 modules/rest-compatibility/src/test/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7Tests.java create mode 100644 server/src/main/java/org/elasticsearch/rest/CompatibleConstants.java diff --git a/build.gradle b/build.gradle index 2dbf94ae7dabb..78322b938e6f4 100644 --- a/build.gradle +++ b/build.gradle @@ -114,6 +114,7 @@ subprojects { ':distribution:tools:keystore-cli', ':distribution:tools:launchers', ':distribution:tools:plugin-cli', + ':modules:rest-compatibility', ':qa:os', ':qa:wildfly', ':x-pack:plugin:autoscaling', 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..25c390c9a1142 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,6 +116,10 @@ public XContent xContent() { } }; + private static final Pattern COMPATIBLE_API_HEADER_PATTERN = Pattern.compile( + "application/(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 @@ -142,7 +148,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 +165,27 @@ public static XContentType fromMediaType(String mediaType) { return null; } + public static String parseMediaType(String mediaType) { + if (mediaType != null) { + Matcher matcher = COMPATIBLE_API_HEADER_PATTERN.matcher(mediaType); + if (matcher.find()) { + return "application/" + matcher.group(2).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(1))) { + + return matcher.group(4); + } + } + 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/rest-compatibility/build.gradle b/modules/rest-compatibility/build.gradle new file mode 100644 index 0000000000000..96e81f07d823a --- /dev/null +++ b/modules/rest-compatibility/build.gradle @@ -0,0 +1,26 @@ +/* + * 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.rest.compat.RestCompatPlugin' +} + +integTest.enabled = false +test.enabled = true diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/RestCompatPlugin.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/RestCompatPlugin.java new file mode 100644 index 0000000000000..18b803f41ab4d --- /dev/null +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/RestCompatPlugin.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.compat; + +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.plugins.ActionPlugin; +import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.rest.RestController; +import org.elasticsearch.rest.RestHandler; +import org.elasticsearch.rest.compat.version7.RestGetActionV7; +import org.elasticsearch.rest.compat.version7.RestIndexActionV7; + +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; + +public class RestCompatPlugin extends Plugin implements ActionPlugin { + + @Override + public List getRestHandlers( + Settings settings, + RestController restController, + ClusterSettings clusterSettings, + IndexScopedSettings indexScopedSettings, + SettingsFilter settingsFilter, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier nodesInCluster + ) { + if (Version.CURRENT.major == 8) { + return List.of( + new RestGetActionV7(), + new RestIndexActionV7.CompatibleRestIndexAction(), + new RestIndexActionV7.CompatibleCreateHandler(), + new RestIndexActionV7.CompatibleAutoIdHandler(nodesInCluster) + ); + } + return Collections.emptyList(); + } +} diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.java new file mode 100644 index 0000000000000..e9ac04ef4afbd --- /dev/null +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.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.rest.compat.version7; + +import org.apache.logging.log4j.LogManager; +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.document.RestGetAction; + +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 = new DeprecationLogger(LogManager.getLogger(RestGetAction.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 List routes() { + assert Version.CURRENT.major == 8 : "REST API compatibility for version 7 is only supported on version 8"; + + 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.deprecatedAndMaybeLog("get_with_types", TYPES_DEPRECATION_MESSAGE); + request.param("type"); + return super.prepareRequest(request, client); + } + + @Override + public boolean compatibilityRequired() { + return true; + } +} diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java new file mode 100644 index 0000000000000..1ce13121a4441 --- /dev/null +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java @@ -0,0 +1,115 @@ +/* + * 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.version7; + +import org.apache.logging.log4j.LogManager; +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 org.elasticsearch.rest.action.document.RestIndexAction; + +import java.io.IOException; +import java.util.List; +import java.util.function.Supplier; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static java.util.Collections.unmodifiableList; +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 = new DeprecationLogger(LogManager.getLogger(RestIndexAction.class)); + + private static void logDeprecationMessage() { + deprecationLogger.deprecatedAndMaybeLog("index_with_types", TYPES_DEPRECATION_MESSAGE); + } + + public static class CompatibleRestIndexAction extends RestIndexAction { + @Override + public List routes() { + assert Version.CURRENT.major == 8 : "REST API compatilbity for version 7 is only supported on version 8"; + + 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 boolean compatibilityRequired() { + return true; + } + } + + public static class CompatibleCreateHandler extends RestIndexAction.CreateHandler { + @Override + public List routes() { + return unmodifiableList( + asList(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 boolean compatibilityRequired() { + return true; + } + } + + public static final class CompatibleAutoIdHandler extends RestIndexAction.AutoIdHandler { + + public CompatibleAutoIdHandler(Supplier nodesInCluster) { + super(nodesInCluster); + } + + @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 boolean compatibilityRequired() { + return true; + } + } +} 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/rest/compat/version7/RestGetActionV7Tests.java b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/compat/version7/RestGetActionV7Tests.java new file mode 100644 index 0000000000000..c84972ad4bb33 --- /dev/null +++ b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/compat/version7/RestGetActionV7Tests.java @@ -0,0 +1,56 @@ +/* + * 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.version7; + +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.test.rest.FakeRestRequest; +import org.elasticsearch.test.rest.RestActionTestCase; +import org.junit.Before; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class RestGetActionV7Tests extends RestActionTestCase { + final String mimeType = randomFrom("application/vnd.elasticsearch+json;compatible-with=7"); + final List contentTypeHeader = Collections.singletonList(mimeType); + + @Before + public void setUpAction() { + controller().registerHandler(new RestGetActionV7()); + } + + public void testTypeInPathWithGet() { + FakeRestRequest.Builder deprecatedRequest = new FakeRestRequest.Builder(xContentRegistry()).withHeaders( + Map.of("Content-Type", contentTypeHeader, "Accept", contentTypeHeader) + ).withPath("/some_index/some_type/some_id"); + dispatchRequest(deprecatedRequest.withMethod(RestRequest.Method.GET).build()); + assertWarnings(RestGetActionV7.TYPES_DEPRECATION_MESSAGE); + } + + public void testTypeInPathWithHead() { + FakeRestRequest.Builder deprecatedRequest = new FakeRestRequest.Builder(xContentRegistry()).withHeaders( + Map.of("Content-Type", contentTypeHeader, "Accept", contentTypeHeader) + ).withPath("/some_index/some_type/some_id"); + dispatchRequest(deprecatedRequest.withMethod(RestRequest.Method.HEAD).build()); + assertWarnings(RestGetActionV7.TYPES_DEPRECATION_MESSAGE); + } + +} diff --git a/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7Tests.java b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7Tests.java new file mode 100644 index 0000000000000..59946abdd3d8e --- /dev/null +++ b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7Tests.java @@ -0,0 +1,76 @@ +/* + * 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.version7; + +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.test.rest.FakeRestRequest; +import org.elasticsearch.test.rest.RestActionTestCase; +import org.junit.Before; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +public class RestIndexActionV7Tests extends RestActionTestCase { + + final String mimeType = randomFrom("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 FakeRestRequest.Builder(xContentRegistry()).withMethod(RestRequest.Method.PUT) + .withHeaders(Map.of("Content-Type", contentTypeHeader, "Accept", contentTypeHeader)) + .withPath("/some_index/some_type/some_id") + .build(); + dispatchRequest(deprecatedRequest); + assertWarnings(RestIndexActionV7.TYPES_DEPRECATION_MESSAGE); + } + + public void testCreateWithTypeInPath() { + // using CompatibleCreateHandler + RestRequest deprecatedRequest = new FakeRestRequest.Builder(xContentRegistry()).withMethod(RestRequest.Method.PUT) + .withHeaders(Map.of("Content-Type", contentTypeHeader, "Accept", contentTypeHeader)) + .withPath("/some_index/some_type/some_id/_create") + .build(); + dispatchRequest(deprecatedRequest); + assertWarnings(RestIndexActionV7.TYPES_DEPRECATION_MESSAGE); + } + + public void testAutoIdWithType() { + // using CompatibleAutoIdHandler + RestRequest deprecatedRequest = new FakeRestRequest.Builder(xContentRegistry()).withMethod(RestRequest.Method.POST) + .withHeaders(Map.of("Content-Type", contentTypeHeader, "Accept", contentTypeHeader)) + .withPath("/some_index/some_type/") + .build(); + dispatchRequest(deprecatedRequest); + assertWarnings(RestIndexActionV7.TYPES_DEPRECATION_MESSAGE); + } +} 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 index 461f6fcd370b0..9cf2a780471fc 100644 --- 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 @@ -22,6 +22,7 @@ import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +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; @@ -29,7 +30,6 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -76,11 +76,21 @@ private static void mutateTestCandidate(ClientYamlTestCandidate testCandidate) { DoSection doSection = (DoSection) ds; //TODO: be more selective here doSection.setIgnoreWarnings(true); - //TODO: use the real header compatibility header - doSection.getApiCallSection().addHeaders(Collections.singletonMap("compatible-with", "v7")); + + String compatibleHeader = createCompatibleHeader(); + //TODO decide which one to use - Accept or Content-Type + doSection.getApiCallSection() + .addHeaders(Map.of( + CompatibleConstants.COMPATIBLE_HEADER, compatibleHeader, + "Content-Type", compatibleHeader + )); }); } + private static String createCompatibleHeader() { + return "application/vnd.elasticsearch+json;compatible-with=" + CompatibleConstants.COMPATIBLE_VERSION; + } + private static Map getLocalCompatibilityTests() throws Exception { Iterable candidates = diff --git a/server/src/main/java/org/elasticsearch/action/DocWriteResponse.java b/server/src/main/java/org/elasticsearch/action/DocWriteResponse.java index 34f9939a366e1..00ab13e24452f 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"; @@ -276,6 +278,9 @@ public void writeTo(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/index/get/GetResult.java b/server/src/main/java/org/elasticsearch/index/get/GetResult.java index 949cc1969e7ae..0e88ad37c673e 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"; @@ -300,6 +304,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..dc378ab0d4117 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); + String compatibleVersion = request.param(CompatibleConstants.COMPATIBLE_PARAMS_KEY); + builder.setCompatibleMajorVersion(compatibleVersion == null ? -1 : Byte.parseByte(compatibleVersion)); return builder; } 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..78b62dac49ba7 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/rest/CompatibleConstants.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; + +import org.elasticsearch.Version; + +public class CompatibleConstants { + + /** + * TODO revisit when https://github.com/elastic/elasticsearch/issues/52370 is resolved + */ + public static final String COMPATIBLE_HEADER = "Accept"; + public static final String COMPATIBLE_PARAMS_KEY = "Compatible-With"; + public static final String COMPATIBLE_VERSION = "" + (Version.CURRENT.major - 1); + +} diff --git a/server/src/main/java/org/elasticsearch/rest/RestController.java b/server/src/main/java/org/elasticsearch/rest/RestController.java index 0537b4d40a044..1f97f8556319f 100644 --- a/server/src/main/java/org/elasticsearch/rest/RestController.java +++ b/server/src/main/java/org/elasticsearch/rest/RestController.java @@ -34,6 +34,7 @@ import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting.Property; import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.core.internal.io.Streams; @@ -55,6 +56,8 @@ import java.util.stream.Collectors; import static org.elasticsearch.rest.BytesRestResponse.TEXT_CONTENT_TYPE; +import static org.elasticsearch.rest.CompatibleConstants.COMPATIBLE_PARAMS_KEY; +import static org.elasticsearch.rest.CompatibleConstants.COMPATIBLE_VERSION; import static org.elasticsearch.rest.RestStatus.BAD_REQUEST; import static org.elasticsearch.rest.RestStatus.INTERNAL_SERVER_ERROR; import static org.elasticsearch.rest.RestStatus.METHOD_NOT_ALLOWED; @@ -328,7 +331,13 @@ private void tryAllHandlers(final RestRequest request, final RestChannel channel return; } } else { - dispatchRequest(request, channel, handler); + if(handler.compatibilityRequired() == false //regular (not removed) handlers are always dispatched + //handlers that were registered compatible, require request to be compatible + || isCompatible(request)) { + dispatchRequest(request, channel, handler); + } else { + handleBadRequest(uri, requestMethod, channel); + } return; } } @@ -340,6 +349,11 @@ private void tryAllHandlers(final RestRequest request, final RestChannel channel handleBadRequest(uri, requestMethod, channel); } + private boolean isCompatible(ToXContent.Params params) { + String param = params.param(COMPATIBLE_PARAMS_KEY); + return COMPATIBLE_VERSION.equals(param); + } + Iterator getAllHandlers(@Nullable Map requestParamsRef, String rawPath) { final Supplier> paramsSupplier; if (requestParamsRef == null) { diff --git a/server/src/main/java/org/elasticsearch/rest/RestHandler.java b/server/src/main/java/org/elasticsearch/rest/RestHandler.java index 0c06a84df62bc..58c452897308e 100644 --- a/server/src/main/java/org/elasticsearch/rest/RestHandler.java +++ b/server/src/main/java/org/elasticsearch/rest/RestHandler.java @@ -89,6 +89,10 @@ default List replacedRoutes() { return Collections.emptyList(); } + default boolean compatibilityRequired(){ + return false; + } + 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..63ebe4bf6e185 100644 --- a/server/src/main/java/org/elasticsearch/rest/RestRequest.java +++ b/server/src/main/java/org/elasticsearch/rest/RestRequest.java @@ -102,6 +102,7 @@ private RestRequest(NamedXContentRegistry xContentRegistry, Map this.rawPath = path; this.headers = Collections.unmodifiableMap(headers); this.requestId = requestId; + addCompatibleParameter(); } protected RestRequest(RestRequest restRequest) { @@ -131,10 +132,30 @@ void ensureSafeBuffers() { public static RestRequest request(NamedXContentRegistry xContentRegistry, HttpRequest httpRequest, HttpChannel httpChannel) { Map params = params(httpRequest.uri()); String path = path(httpRequest.uri()); - return new RestRequest(xContentRegistry, params, path, httpRequest.getHeaders(), httpRequest, httpChannel, + RestRequest restRequest = new RestRequest(xContentRegistry, params, path, httpRequest.getHeaders(), httpRequest, httpChannel, requestIdGenerator.incrementAndGet()); + return restRequest; } + private void addCompatibleParameter() { + if (isRequestCompatible()) { + String compatibleVersion = XContentType.parseVersion(header(CompatibleConstants.COMPATIBLE_HEADER)); + params().put(CompatibleConstants.COMPATIBLE_PARAMS_KEY, compatibleVersion); + //use it so it won't fail request validation with unused parameter + param(CompatibleConstants.COMPATIBLE_PARAMS_KEY); + } + } + + private boolean isRequestCompatible() { + return isHeaderCompatible(header(CompatibleConstants.COMPATIBLE_HEADER)); + } + + private boolean isHeaderCompatible(String headerValue) { + String version = XContentType.parseVersion(headerValue); + return CompatibleConstants.COMPATIBLE_VERSION.equals(version); + } + + private static Map params(final String uri) { final Map params = new HashMap<>(); int index = uri.indexOf('?'); 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 f155b09aafb42..326d3d46f29c4 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/test/java/org/elasticsearch/common/xcontent/XContentTypeTests.java b/server/src/test/java/org/elasticsearch/common/xcontent/XContentTypeTests.java index 47a470e2cea84..856e544a23055 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,41 @@ 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()); + } } diff --git a/server/src/test/java/org/elasticsearch/http/DefaultRestChannelTests.java b/server/src/test/java/org/elasticsearch/http/DefaultRestChannelTests.java index 0f82be7f23b02..8d7a026aee034 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; @@ -36,6 +38,7 @@ import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.indices.breaker.NoneCircuitBreakerService; import org.elasticsearch.rest.BytesRestResponse; +import org.elasticsearch.rest.CompatibleConstants; import org.elasticsearch.rest.RestChannel; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.rest.RestResponse; @@ -212,6 +215,15 @@ public void testHeadersSet() { assertEquals(resp.contentType(), headers.get(DefaultRestChannel.CONTENT_TYPE).get(0)); } + public void testCompatibleParamIsSet() { + String version = String.valueOf(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=" + version)); + final RestRequest request = RestRequest.request(xContentRegistry(), httpRequest, httpChannel); + + assertEquals(version, request.param(CompatibleConstants.COMPATIBLE_PARAMS_KEY)); + } + 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 +416,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/RestControllerTests.java b/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java index dfcfd644dde34..0d51a0fcdbaeb 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; @@ -623,6 +624,41 @@ public void testDispatchRestrictSystemIndices() { assertFalse(context.isSystemIndexAccessAllowed()); } + public void testDispatchCompatibleHandler() { + final byte version = (byte) (Version.CURRENT.major - 1); + + final String mimeType = randomFrom("application/vnd.elasticsearch+json;compatible-with="+version); + String content = randomAlphaOfLength((int) Math.round(BREAKER_LIMIT.getBytes() / inFlightRequestsBreaker.getOverhead())); + final List contentTypeHeader = Collections.singletonList(mimeType); + FakeRestRequest fakeRestRequest = new FakeRestRequest.Builder(NamedXContentRegistry.EMPTY) + .withContent(new BytesArray(content), RestRequest.parseContentType(contentTypeHeader)).withPath("/foo") + .withHeaders(Map.of("Content-Type", contentTypeHeader, "Accept", contentTypeHeader)) + .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 boolean supportsContentStream() { + return true; + } + + @Override + public boolean compatibilityRequired() { + return true; + } + }); + + assertFalse(channel.getSendResponseCalled()); + restController.dispatchRequest(fakeRestRequest, channel, new ThreadContext(Settings.EMPTY)); + assertTrue(channel.getSendResponseCalled()); + } + private static final class TestHttpServerTransport extends AbstractLifecycleComponent implements HttpServerTransport { 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 2b90dcb26887a..f920c8d54580a 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 @@ -185,9 +185,13 @@ public static Iterable createParameters() throws Exception { return createParameters(ExecutableSection.XCONTENT_REGISTRY, TESTS_PATH); } - /** - * Create parameters for this parameterized test. - */ + 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(testsPath, paths); 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/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 a2b159c1024b0..6a9a38d1f7565 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 @@ -127,4 +127,9 @@ private RestRequest maybeWrapRestRequest(RestRequest restRequest) throws IOExcep } return restRequest; } + + @Override + public boolean compatibilityRequired() { + return restHandler.compatibilityRequired(); + } } From d8a900d3f8ee8a1cc57d175e0f55cda88d844388 Mon Sep 17 00:00:00 2001 From: Przemyslaw Gomulka Date: Mon, 30 Mar 2020 10:36:05 +0200 Subject: [PATCH 05/16] update actions to use supplier for nodes --- .../org/elasticsearch/action/ActionModule.java | 14 +++++++------- .../rest/action/document/RestIndexAction.java | 11 ++++++----- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/action/ActionModule.java b/server/src/main/java/org/elasticsearch/action/ActionModule.java index 2ceab348ebff2..2dff237d7125a 100644 --- a/server/src/main/java/org/elasticsearch/action/ActionModule.java +++ b/server/src/main/java/org/elasticsearch/action/ActionModule.java @@ -28,9 +28,6 @@ import org.elasticsearch.action.admin.cluster.configuration.ClearVotingConfigExclusionsAction; import org.elasticsearch.action.admin.cluster.configuration.TransportAddVotingConfigExclusionsAction; import org.elasticsearch.action.admin.cluster.configuration.TransportClearVotingConfigExclusionsAction; -import org.elasticsearch.action.admin.indices.datastream.DeleteDataStreamAction; -import org.elasticsearch.action.admin.indices.datastream.GetDataStreamsAction; -import org.elasticsearch.action.admin.indices.datastream.CreateDataStreamAction; import org.elasticsearch.action.admin.cluster.health.ClusterHealthAction; import org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction; import org.elasticsearch.action.admin.cluster.node.hotthreads.NodesHotThreadsAction; @@ -108,6 +105,9 @@ import org.elasticsearch.action.admin.indices.close.TransportVerifyShardBeforeCloseAction; import org.elasticsearch.action.admin.indices.create.CreateIndexAction; import org.elasticsearch.action.admin.indices.create.TransportCreateIndexAction; +import org.elasticsearch.action.admin.indices.datastream.CreateDataStreamAction; +import org.elasticsearch.action.admin.indices.datastream.DeleteDataStreamAction; +import org.elasticsearch.action.admin.indices.datastream.GetDataStreamsAction; import org.elasticsearch.action.admin.indices.delete.DeleteIndexAction; import org.elasticsearch.action.admin.indices.delete.TransportDeleteIndexAction; import org.elasticsearch.action.admin.indices.flush.FlushAction; @@ -260,11 +260,9 @@ import org.elasticsearch.rest.action.admin.cluster.RestClusterStatsAction; import org.elasticsearch.rest.action.admin.cluster.RestClusterUpdateSettingsAction; import org.elasticsearch.rest.action.admin.cluster.RestCreateSnapshotAction; -import org.elasticsearch.rest.action.admin.indices.RestDeleteDataStreamAction; import org.elasticsearch.rest.action.admin.cluster.RestDeleteRepositoryAction; import org.elasticsearch.rest.action.admin.cluster.RestDeleteSnapshotAction; import org.elasticsearch.rest.action.admin.cluster.RestDeleteStoredScriptAction; -import org.elasticsearch.rest.action.admin.indices.RestGetDataStreamsAction; import org.elasticsearch.rest.action.admin.cluster.RestGetRepositoriesAction; import org.elasticsearch.rest.action.admin.cluster.RestGetScriptContextAction; import org.elasticsearch.rest.action.admin.cluster.RestGetScriptLanguageAction; @@ -277,7 +275,6 @@ import org.elasticsearch.rest.action.admin.cluster.RestNodesStatsAction; import org.elasticsearch.rest.action.admin.cluster.RestNodesUsageAction; import org.elasticsearch.rest.action.admin.cluster.RestPendingClusterTasksAction; -import org.elasticsearch.rest.action.admin.indices.RestCreateDataStreamAction; import org.elasticsearch.rest.action.admin.cluster.RestPutRepositoryAction; import org.elasticsearch.rest.action.admin.cluster.RestPutStoredScriptAction; import org.elasticsearch.rest.action.admin.cluster.RestReloadSecureSettingsAction; @@ -288,8 +285,10 @@ import org.elasticsearch.rest.action.admin.indices.RestAnalyzeAction; import org.elasticsearch.rest.action.admin.indices.RestClearIndicesCacheAction; import org.elasticsearch.rest.action.admin.indices.RestCloseIndexAction; +import org.elasticsearch.rest.action.admin.indices.RestCreateDataStreamAction; import org.elasticsearch.rest.action.admin.indices.RestCreateIndexAction; import org.elasticsearch.rest.action.admin.indices.RestDeleteComponentTemplateAction; +import org.elasticsearch.rest.action.admin.indices.RestDeleteDataStreamAction; import org.elasticsearch.rest.action.admin.indices.RestDeleteIndexAction; import org.elasticsearch.rest.action.admin.indices.RestDeleteIndexTemplateAction; import org.elasticsearch.rest.action.admin.indices.RestDeleteIndexTemplateV2Action; @@ -297,6 +296,7 @@ import org.elasticsearch.rest.action.admin.indices.RestForceMergeAction; import org.elasticsearch.rest.action.admin.indices.RestGetAliasesAction; import org.elasticsearch.rest.action.admin.indices.RestGetComponentTemplateAction; +import org.elasticsearch.rest.action.admin.indices.RestGetDataStreamsAction; import org.elasticsearch.rest.action.admin.indices.RestGetFieldMappingAction; import org.elasticsearch.rest.action.admin.indices.RestGetIndexTemplateAction; import org.elasticsearch.rest.action.admin.indices.RestGetIndexTemplateV2Action; @@ -716,7 +716,7 @@ public void initRestHandlers(Supplier nodesInCluster) { registerHandler.accept(new RestIndexAction()); registerHandler.accept(new CreateHandler()); - registerHandler.accept(new AutoIdHandler(clusterService)); + registerHandler.accept(new AutoIdHandler(nodesInCluster)); registerHandler.accept(new RestGetAction()); registerHandler.accept(new RestGetSourceAction()); registerHandler.accept(new RestMultiGetAction(settings)); 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 7aea955036db6..326d3d46f29c4 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 @@ -23,7 +23,7 @@ import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.support.ActiveShardCount; import org.elasticsearch.client.node.NodeClient; -import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.cluster.node.DiscoveryNodes; import org.elasticsearch.index.VersionType; import org.elasticsearch.rest.BaseRestHandler; import org.elasticsearch.rest.RestRequest; @@ -33,6 +33,7 @@ import java.io.IOException; import java.util.List; import java.util.Locale; +import java.util.function.Supplier; import static org.elasticsearch.rest.RestRequest.Method.POST; import static org.elasticsearch.rest.RestRequest.Method.PUT; @@ -80,10 +81,10 @@ void validateOpType(String opType) { public static class AutoIdHandler extends RestIndexAction { - private final ClusterService clusterService; + private final Supplier nodesInCluster; - public AutoIdHandler(ClusterService clusterService) { - this.clusterService = clusterService; + public AutoIdHandler(Supplier nodesInCluster) { + this.nodesInCluster = nodesInCluster; } @Override @@ -99,7 +100,7 @@ public List routes() { @Override public RestChannelConsumer prepareRequest(RestRequest request, final NodeClient client) throws IOException { assert request.params().get("id") == null : "non-null id: " + request.params().get("id"); - if (request.params().get("op_type") == null && clusterService.state().nodes().getMinNodeVersion().onOrAfter(Version.V_7_5_0)) { + if (request.params().get("op_type") == null && nodesInCluster.get().getMinNodeVersion().onOrAfter(Version.V_7_5_0)) { // default to op_type create request.params().put("op_type", "create"); } From 36ac271abcb2cc84f4ebae368d01d6537ddd084a Mon Sep 17 00:00:00 2001 From: Przemyslaw Gomulka Date: Wed, 8 Apr 2020 15:32:54 +0200 Subject: [PATCH 06/16] fix --- .../rest/compat/version7/RestGetActionV7.java | 5 +++++ .../rest/compat/version7/RestIndexActionV7.java | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.java index e9ac04ef4afbd..554ea947b8f2a 100644 --- a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.java +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.java @@ -37,6 +37,11 @@ public class RestGetActionV7 extends RestGetAction { private static final DeprecationLogger deprecationLogger = new DeprecationLogger(LogManager.getLogger(RestGetAction.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 "document_get_action_v7"; + } + @Override public List routes() { diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java index 1ce13121a4441..33e52ca5e4cc6 100644 --- a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java @@ -48,6 +48,11 @@ private static void logDeprecationMessage() { } public static class CompatibleRestIndexAction extends RestIndexAction { + @Override + public String getName() { + return "document_index_action_v7"; + } + @Override public List routes() { assert Version.CURRENT.major == 8 : "REST API compatilbity for version 7 is only supported on version 8"; @@ -69,6 +74,11 @@ public boolean compatibilityRequired() { } public static class CompatibleCreateHandler extends RestIndexAction.CreateHandler { + @Override + public String getName() { + return "document_create_action_v7"; + } + @Override public List routes() { return unmodifiableList( @@ -90,6 +100,10 @@ public boolean compatibilityRequired() { } public static final class CompatibleAutoIdHandler extends RestIndexAction.AutoIdHandler { + @Override + public String getName() { + return "document_create_action_auto_id_v7"; + } public CompatibleAutoIdHandler(Supplier nodesInCluster) { super(nodesInCluster); From 3b3da9d6085231b7d0da69a12571d96964a71781 Mon Sep 17 00:00:00 2001 From: Przemyslaw Gomulka Date: Mon, 20 Apr 2020 10:36:07 +0200 Subject: [PATCH 07/16] Validate Accept and Content-Type header for compatible API (#54103) Adding validation of Accept And Content-Type headers. The idea is to verify combinations of presence and values of both headers depending if a request has a content, headers are versioned (type/vnd.elasticsearch+subtype;compatible-with) . It also changes the way media type is parsed (previously always assuming application as a type i.e application/json) It should expect different types like text - used in SQL Not adding a compatible headers for _cat api. These in order to return a default text do not expect Accept header (Content-type is not needed as content is not present) See #53228 (comment) for more context --- .../common/xcontent/XContentType.java | 15 +- .../rest/compat/version7/RestGetActionV7.java | 2 +- .../compat/version7/RestIndexActionV7.java | 10 +- .../rest/Netty4BadRequestIT.java | 23 ++ .../rest/compat/AbstractCompatRestTest.java | 42 +-- .../main/java/org/elasticsearch/Version.java | 4 + .../http/AbstractHttpServerTransport.java | 8 +- .../rest/CompatibleConstants.java | 5 +- .../org/elasticsearch/rest/RestRequest.java | 111 ++++++-- .../common/xcontent/XContentTypeTests.java | 17 ++ .../CompatibleHeaderCombinationTests.java | 242 ++++++++++++++++++ .../rest/RestControllerTests.java | 22 +- .../test/rest/FakeRestRequest.java | 13 + .../test/rest/yaml/ClientYamlTestClient.java | 6 + .../yaml/ClientYamlTestExecutionContext.java | 18 +- .../xpack/sql/plugin/TextFormat.java | 1 + 16 files changed, 478 insertions(+), 61 deletions(-) create mode 100644 server/src/test/java/org/elasticsearch/rest/CompatibleHeaderCombinationTests.java 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 25c390c9a1142..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 @@ -117,7 +117,7 @@ public XContent xContent() { }; private static final Pattern COMPATIBLE_API_HEADER_PATTERN = Pattern.compile( - "application/(vnd.elasticsearch\\+)?([^;]+)(\\s*;\\s*compatible-with=(\\d+))?", + "(application|text)/(vnd.elasticsearch\\+)?([^;]+)(\\s*;\\s*compatible-with=(\\d+))?", Pattern.CASE_INSENSITIVE); /** @@ -126,7 +126,9 @@ public XContent xContent() { * 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; } @@ -136,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; } @@ -165,11 +167,12 @@ public static XContentType fromMediaType(String mediaTypeHeaderValue) { 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 "application/" + matcher.group(2).toLowerCase(Locale.ROOT); + return (matcher.group(1) + "/" + matcher.group(3)).toLowerCase(Locale.ROOT); } } @@ -179,9 +182,9 @@ public static String parseMediaType(String 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(1))) { + if (matcher.find() && "vnd.elasticsearch+".equalsIgnoreCase(matcher.group(2))) { - return matcher.group(4); + return matcher.group(5); } } return null; diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.java index 554ea947b8f2a..d8d01f42698f7 100644 --- a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.java +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.java @@ -37,12 +37,12 @@ public class RestGetActionV7 extends RestGetAction { private static final DeprecationLogger deprecationLogger = new DeprecationLogger(LogManager.getLogger(RestGetAction.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 "document_get_action_v7"; } - @Override public List routes() { assert Version.CURRENT.major == 8 : "REST API compatibility for version 7 is only supported on version 8"; diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java index 33e52ca5e4cc6..51d4a56baa8fa 100644 --- a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java @@ -74,6 +74,7 @@ public boolean compatibilityRequired() { } public static class CompatibleCreateHandler extends RestIndexAction.CreateHandler { + @Override public String getName() { return "document_create_action_v7"; @@ -100,15 +101,16 @@ public boolean compatibilityRequired() { } public static final class CompatibleAutoIdHandler extends RestIndexAction.AutoIdHandler { - @Override - public String getName() { - return "document_create_action_auto_id_v7"; - } 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}")); 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/src/main/java/org/elasticsearch/rest/compat/AbstractCompatRestTest.java b/qa/rest-compat-tests/src/main/java/org/elasticsearch/rest/compat/AbstractCompatRestTest.java index 9cf2a780471fc..36bfd8b56c45f 100644 --- 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 @@ -33,6 +33,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import java.util.stream.StreamSupport; /** @@ -43,7 +44,6 @@ protected AbstractCompatRestTest(@Name("yaml") ClientYamlTestCandidate testCandi super(testCandidate); } - private static final Logger staticLogger = LogManager.getLogger(AbstractCompatRestTest.class); public static final String COMPAT_TESTS_PATH = "/rest-api-spec/test-compat"; @@ -66,38 +66,48 @@ public static Iterable createParameters() throws Exception { }); finalTestCandidates.add(testCandidates.toArray()); } - localCandidates.keySet().forEach(lc -> finalTestCandidates.add(new Object[]{lc})); + localCandidates.keySet().forEach(lc -> finalTestCandidates.add(new Object[] { lc })); return finalTestCandidates; } - private static void mutateTestCandidate(ClientYamlTestCandidate testCandidate) { - testCandidate.getTestSection().getExecutableSections().stream().filter(s -> s instanceof DoSection).forEach(ds -> { + 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 + // TODO: be more selective here doSection.setIgnoreWarnings(true); String compatibleHeader = createCompatibleHeader(); - //TODO decide which one to use - Accept or Content-Type - doSection.getApiCallSection() - .addHeaders(Map.of( - CompatibleConstants.COMPATIBLE_HEADER, compatibleHeader, - "Content-Type", compatibleHeader - )); - }); + // 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=" + CompatibleConstants.COMPATIBLE_VERSION; } - private static Map getLocalCompatibilityTests() throws Exception { - Iterable candidates = - ESClientYamlSuiteTestCase.createParameters(ExecutableSection.XCONTENT_REGISTRY, COMPAT_TESTS_PATH); + 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)); + .flatMap(Arrays::stream) + .forEach(o -> localCompatibilityTests.put((ClientYamlTestCandidate) o, (ClientYamlTestCandidate) o)); return localCompatibilityTests; } } diff --git a/server/src/main/java/org/elasticsearch/Version.java b/server/src/main/java/org/elasticsearch/Version.java index 168c823e3f2ac..99f4b57935c2b 100644 --- a/server/src/main/java/org/elasticsearch/Version.java +++ b/server/src/main/java/org/elasticsearch/Version.java @@ -342,6 +342,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/http/AbstractHttpServerTransport.java b/server/src/main/java/org/elasticsearch/http/AbstractHttpServerTransport.java index 8d02eac5b8b13..f30acc2e3e8b7 100644 --- a/server/src/main/java/org/elasticsearch/http/AbstractHttpServerTransport.java +++ b/server/src/main/java/org/elasticsearch/http/AbstractHttpServerTransport.java @@ -352,6 +352,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; } @@ -384,12 +387,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/rest/CompatibleConstants.java b/server/src/main/java/org/elasticsearch/rest/CompatibleConstants.java index 78b62dac49ba7..2f3f13b893fe3 100644 --- a/server/src/main/java/org/elasticsearch/rest/CompatibleConstants.java +++ b/server/src/main/java/org/elasticsearch/rest/CompatibleConstants.java @@ -26,8 +26,9 @@ public class CompatibleConstants { /** * TODO revisit when https://github.com/elastic/elasticsearch/issues/52370 is resolved */ - public static final String COMPATIBLE_HEADER = "Accept"; + public static final String COMPATIBLE_ACCEPT_HEADER = "Accept"; + public static final String COMPATIBLE_CONTENT_TYPE_HEADER = "Content-Type"; public static final String COMPATIBLE_PARAMS_KEY = "Compatible-With"; - public static final String COMPATIBLE_VERSION = "" + (Version.CURRENT.major - 1); + public static final String COMPATIBLE_VERSION = String.valueOf(Version.minimumRestCompatibilityVersion().major); } diff --git a/server/src/main/java/org/elasticsearch/rest/RestRequest.java b/server/src/main/java/org/elasticsearch/rest/RestRequest.java index 63ebe4bf6e185..cd52770fd6683 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; @@ -86,6 +88,12 @@ 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,7 +110,7 @@ private RestRequest(NamedXContentRegistry xContentRegistry, Map this.rawPath = path; this.headers = Collections.unmodifiableMap(headers); this.requestId = requestId; - addCompatibleParameter(); + addCompatibleParameter(headersValidation); } protected RestRequest(RestRequest restRequest) { @@ -132,29 +140,61 @@ void ensureSafeBuffers() { public static RestRequest request(NamedXContentRegistry xContentRegistry, HttpRequest httpRequest, HttpChannel httpChannel) { Map params = params(httpRequest.uri()); String path = path(httpRequest.uri()); - RestRequest restRequest = new RestRequest(xContentRegistry, params, path, httpRequest.getHeaders(), httpRequest, httpChannel, + return new RestRequest(xContentRegistry, params, path, httpRequest.getHeaders(), httpRequest, httpChannel, requestIdGenerator.incrementAndGet()); - return restRequest; } - private void addCompatibleParameter() { - if (isRequestCompatible()) { - String compatibleVersion = XContentType.parseVersion(header(CompatibleConstants.COMPATIBLE_HEADER)); - params().put(CompatibleConstants.COMPATIBLE_PARAMS_KEY, compatibleVersion); - //use it so it won't fail request validation with unused parameter - param(CompatibleConstants.COMPATIBLE_PARAMS_KEY); + private void addCompatibleParameter(boolean headersValidation) { + if(headersValidation){ + if(isRequestingCompatibility()) { + params().put(CompatibleConstants.COMPATIBLE_PARAMS_KEY, + String.valueOf(Version.minimumRestCompatibilityVersion().major)); + //use it so it won't fail request validation with unused parameter + param(CompatibleConstants.COMPATIBLE_PARAMS_KEY); + } } } + 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())); + } - private boolean isRequestCompatible() { - return isHeaderCompatible(header(CompatibleConstants.COMPATIBLE_HEADER)); - } + 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())); + } - private boolean isHeaderCompatible(String headerValue) { - String version = XContentType.parseVersion(headerValue); - return CompatibleConstants.COMPATIBLE_VERSION.equals(version); - } + return contentTypeVersion < Version.CURRENT.major; + } + return acceptVersion < Version.CURRENT.major; + } private static Map params(final String uri) { final Map params = new HashMap<>(); @@ -191,9 +231,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 } @@ -559,4 +628,12 @@ public static class BadParameterException extends RuntimeException { } + public static class CompatibleApiHeadersCombinationException extends RuntimeException { + + CompatibleApiHeadersCombinationException(String cause) { + super(cause); + } + + } + } 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 856e544a23055..3ceb93ef6abec 100644 --- a/server/src/test/java/org/elasticsearch/common/xcontent/XContentTypeTests.java +++ b/server/src/test/java/org/elasticsearch/common/xcontent/XContentTypeTests.java @@ -121,4 +121,21 @@ public void testVersionParsing() { 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/rest/CompatibleHeaderCombinationTests.java b/server/src/test/java/org/elasticsearch/rest/CompatibleHeaderCombinationTests.java new file mode 100644 index 0000000000000..e38b7f9efab38 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/rest/CompatibleHeaderCombinationTests.java @@ -0,0 +1,242 @@ +/* + * 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(build -> + build.param(CompatibleConstants.COMPATIBLE_PARAMS_KEY) //TODO to be refactored into getVersion + , equalTo(String.valueOf(version))); + } + + 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 22df41f20df83..4811dbc466623 100644 --- a/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java +++ b/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java @@ -58,6 +58,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; @@ -608,12 +609,13 @@ public HttpRequest releaseAndCopy() { public void testDispatchCompatibleHandler() { final byte version = (byte) (Version.CURRENT.major - 1); - final String mimeType = randomFrom("application/vnd.elasticsearch+json;compatible-with="+version); + final String mimeType = randomCompatibleMimeType(version); String content = randomAlphaOfLength((int) Math.round(BREAKER_LIMIT.getBytes() / inFlightRequestsBreaker.getOverhead())); - final List contentTypeHeader = Collections.singletonList(mimeType); + final List mimeTypeList = Collections.singletonList(mimeType); FakeRestRequest fakeRestRequest = new FakeRestRequest.Builder(NamedXContentRegistry.EMPTY) - .withContent(new BytesArray(content), RestRequest.parseContentType(contentTypeHeader)).withPath("/foo") - .withHeaders(Map.of("Content-Type", contentTypeHeader, "Accept", contentTypeHeader)) + .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() { @@ -624,11 +626,6 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c channel.sendResponse(new BytesRestResponse(RestStatus.OK, BytesRestResponse.TEXT_CONTENT_TYPE, BytesArray.EMPTY)); } - @Override - public boolean supportsContentStream() { - return true; - } - @Override public boolean compatibilityRequired() { return true; @@ -640,6 +637,13 @@ public boolean compatibilityRequired() { assertTrue(channel.getSendResponseCalled()); } + 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 2f2f5fb76bfe7..093b5ce28651b 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 @@ -219,5 +219,18 @@ public FakeRestRequest build() { FakeHttpRequest fakeHttpRequest = new FakeHttpRequest(method, path, content, headers); 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 + + '}'; + } } } 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/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) From 3a8303ece3e9d3cb47ae6348935c379083fe578f Mon Sep 17 00:00:00 2001 From: Przemyslaw Gomulka Date: Tue, 28 Apr 2020 15:24:38 +0200 Subject: [PATCH 08/16] Compatible logic for include_type_param and RestCreateIndexAction (#54197) Refactoring of the compatible infrastructure to allow registering multiple RestAction under the same path. It only extends the current mechanism which allowed registering RestAction under the same path but with different method. Now it also uses a version together with a method to find a matching RestAction This PR also provides a V7 compatible RestCreateIndexAction that needs include_Type_param and a different logic for parsing mapping from its body. fixed get/index tests CompatRestIT. test {yaml=get/21_stored_fields_with_types/Stored fields} CompatRestIT. test {yaml=get/71_source_filtering_with_types/Source filtering} CompatRestIT. test {yaml=index/70_mix_typeless_typeful/Index call that introduces new field mappings} CompatRestIT. test {yaml=index/70_mix_typeless_typeful/Index with typeless API on an index that has types} however the last one from get is still failing CompatRestIT. test {yaml=get/100_mix_typeless_typeful/GET with typeless API on an index that has relates #54160 --- .../rest/compat/RestCompatPlugin.java | 4 +- .../version7/RestCreateIndexActionV7.java | 119 ++++++++++++++++++ .../rest/compat/version7/RestGetActionV7.java | 6 +- .../compat/version7/RestIndexActionV7.java | 20 ++- .../RestCreateIndexActionV7Tests.java | 75 +++++++++++ .../compat/version7/RestGetActionV7Tests.java | 2 +- .../version7/RestIndexActionV7Tests.java | 2 +- .../rest/compat/AbstractCompatRestTest.java | 5 +- .../rest/AbstractRestChannel.java | 4 +- .../rest/CompatibleConstants.java | 4 - .../elasticsearch/rest/MethodHandlers.java | 34 +++-- .../elasticsearch/rest/RestController.java | 30 ++--- .../org/elasticsearch/rest/RestHandler.java | 11 +- .../org/elasticsearch/rest/RestRequest.java | 30 +++-- .../http/DefaultRestChannelTests.java | 7 +- .../CompatibleHeaderCombinationTests.java | 5 +- .../rest/RestControllerTests.java | 35 +++++- .../test/rest/FakeRestRequest.java | 2 +- .../test/rest/RestActionTestCase.java | 2 +- .../security/rest/SecurityRestFilter.java | 5 +- 20 files changed, 318 insertions(+), 84 deletions(-) create mode 100644 modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestCreateIndexActionV7.java create mode 100644 modules/rest-compatibility/src/test/java/org/elasticsearch/rest/compat/version7/RestCreateIndexActionV7Tests.java diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/RestCompatPlugin.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/RestCompatPlugin.java index 18b803f41ab4d..3fe0c356a5a34 100644 --- a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/RestCompatPlugin.java +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/RestCompatPlugin.java @@ -30,6 +30,7 @@ import org.elasticsearch.plugins.Plugin; import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestHandler; +import org.elasticsearch.rest.compat.version7.RestCreateIndexActionV7; import org.elasticsearch.rest.compat.version7.RestGetActionV7; import org.elasticsearch.rest.compat.version7.RestIndexActionV7; @@ -54,7 +55,8 @@ public List getRestHandlers( new RestGetActionV7(), new RestIndexActionV7.CompatibleRestIndexAction(), new RestIndexActionV7.CompatibleCreateHandler(), - new RestIndexActionV7.CompatibleAutoIdHandler(nodesInCluster) + new RestIndexActionV7.CompatibleAutoIdHandler(nodesInCluster), + new RestCreateIndexActionV7() ); } return Collections.emptyList(); diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestCreateIndexActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestCreateIndexActionV7.java new file mode 100644 index 0000000000000..6f5d3d1fe272f --- /dev/null +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/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.compat.version7; + +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 org.elasticsearch.rest.action.admin.indices.RestCreateIndexAction; + +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"; + + @Override + public String getName() { + return "create_index_action_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 = prepareRequest(request); + return channel -> client.admin().indices().create(createIndexRequest, new RestToXContentListener<>(channel)); + } + + // default scope for testing + CreateIndexRequest prepareRequest(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 = prepareMappings(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 prepareMappings(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/compat/version7/RestGetActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.java index d8d01f42698f7..b0eda49b522c5 100644 --- a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.java +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.java @@ -45,8 +45,6 @@ public String getName() { @Override public List routes() { - assert Version.CURRENT.major == 8 : "REST API compatibility for version 7 is only supported on version 8"; - return List.of(new Route(GET, "/{index}/{type}/{id}"), new Route(HEAD, "/{index}/{type}/{id}")); } @@ -58,7 +56,7 @@ public RestChannelConsumer prepareRequest(RestRequest request, final NodeClient } @Override - public boolean compatibilityRequired() { - return true; + public Version compatibleWithVersion() { + return Version.V_7_0_0; } } diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java index 51d4a56baa8fa..33d9ff76c2cc7 100644 --- a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java @@ -31,9 +31,7 @@ import java.util.List; import java.util.function.Supplier; -import static java.util.Arrays.asList; import static java.util.Collections.singletonList; -import static java.util.Collections.unmodifiableList; import static org.elasticsearch.rest.RestRequest.Method.POST; import static org.elasticsearch.rest.RestRequest.Method.PUT; @@ -55,8 +53,6 @@ public String getName() { @Override public List routes() { - assert Version.CURRENT.major == 8 : "REST API compatilbity for version 7 is only supported on version 8"; - return List.of(new Route(POST, "/{index}/{type}/{id}"), new Route(PUT, "/{index}/{type}/{id}")); } @@ -68,8 +64,8 @@ public RestChannelConsumer prepareRequest(RestRequest request, final NodeClient } @Override - public boolean compatibilityRequired() { - return true; + public Version compatibleWithVersion() { + return Version.V_7_0_0; } } @@ -82,9 +78,7 @@ public String getName() { @Override public List routes() { - return unmodifiableList( - asList(new Route(POST, "/{index}/{type}/{id}/_create"), new Route(PUT, "/{index}/{type}/{id}/_create")) - ); + return List.of(new Route(POST, "/{index}/{type}/{id}/_create"), new Route(PUT, "/{index}/{type}/{id}/_create")); } @Override @@ -95,8 +89,8 @@ public RestChannelConsumer prepareRequest(RestRequest request, final NodeClient } @Override - public boolean compatibilityRequired() { - return true; + public Version compatibleWithVersion() { + return Version.V_7_0_0; } } @@ -124,8 +118,8 @@ public RestChannelConsumer prepareRequest(RestRequest request, final NodeClient } @Override - public boolean compatibilityRequired() { - return true; + public Version compatibleWithVersion() { + return Version.V_7_0_0; } } } diff --git a/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/compat/version7/RestCreateIndexActionV7Tests.java b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/compat/version7/RestCreateIndexActionV7Tests.java new file mode 100644 index 0000000000000..781ed2e12e3cc --- /dev/null +++ b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/compat/version7/RestCreateIndexActionV7Tests.java @@ -0,0 +1,75 @@ +/* + * 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.version7; + +import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.test.rest.FakeRestRequest; +import org.elasticsearch.test.rest.RestActionTestCase; +import org.junit.Before; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.hamcrest.Matchers.equalTo; + +public class RestCreateIndexActionV7Tests extends RestActionTestCase { + + String mimeType = "application/vnd.elasticsearch+json;compatible-with=7"; + List contentTypeHeader = Collections.singletonList(mimeType); + + 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 FakeRestRequest.Builder(xContentRegistry()).withMethod(RestRequest.Method.PUT) + .withHeaders(Map.of("Content-Type", contentTypeHeader, "Accept", contentTypeHeader)) + .withPath("/some_index") + .withParams(params) + .withContent(new BytesArray(content), null) + .build(); + + CreateIndexRequest createIndexRequest = restHandler.prepareRequest(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/compat/version7/RestGetActionV7Tests.java b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/compat/version7/RestGetActionV7Tests.java index c84972ad4bb33..124138ea2a142 100644 --- a/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/compat/version7/RestGetActionV7Tests.java +++ b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/compat/version7/RestGetActionV7Tests.java @@ -29,7 +29,7 @@ import java.util.Map; public class RestGetActionV7Tests extends RestActionTestCase { - final String mimeType = randomFrom("application/vnd.elasticsearch+json;compatible-with=7"); + final String mimeType = "application/vnd.elasticsearch+json;compatible-with=7"; final List contentTypeHeader = Collections.singletonList(mimeType); @Before diff --git a/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7Tests.java b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7Tests.java index 59946abdd3d8e..6fb7b2dfac2f2 100644 --- a/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7Tests.java +++ b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7Tests.java @@ -32,7 +32,7 @@ public class RestIndexActionV7Tests extends RestActionTestCase { - final String mimeType = randomFrom("application/vnd.elasticsearch+json;compatible-with=7"); + final String mimeType = "application/vnd.elasticsearch+json;compatible-with=7"; final List contentTypeHeader = Collections.singletonList(mimeType); private final AtomicReference clusterStateSupplier = new AtomicReference<>(); 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 index 36bfd8b56c45f..a770139c44aa3 100644 --- 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 @@ -22,6 +22,7 @@ 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; @@ -82,7 +83,7 @@ private static Consumer updateDoSection() { doSection.setIgnoreWarnings(true); String compatibleHeader = createCompatibleHeader(); - // for cat apis accept headers would break tests which expect txt response + // TODO for cat apis accept headers would break tests which expect txt response if (doSection.getApiCallSection().getApi().startsWith("cat") == false) { doSection.getApiCallSection() .addHeaders( @@ -99,7 +100,7 @@ private static Consumer updateDoSection() { } private static String createCompatibleHeader() { - return "application/vnd.elasticsearch+json;compatible-with=" + CompatibleConstants.COMPATIBLE_VERSION; + return "application/vnd.elasticsearch+json;compatible-with=" + Version.minimumRestCompatibilityVersion().major; } private static Map getLocalCompatibilityTests() throws Exception { diff --git a/server/src/main/java/org/elasticsearch/rest/AbstractRestChannel.java b/server/src/main/java/org/elasticsearch/rest/AbstractRestChannel.java index dc378ab0d4117..7a6abc34d6b60 100644 --- a/server/src/main/java/org/elasticsearch/rest/AbstractRestChannel.java +++ b/server/src/main/java/org/elasticsearch/rest/AbstractRestChannel.java @@ -127,8 +127,8 @@ public XContentBuilder newBuilder(@Nullable XContentType requestContentType, @Nu } builder.humanReadable(human); - String compatibleVersion = request.param(CompatibleConstants.COMPATIBLE_PARAMS_KEY); - builder.setCompatibleMajorVersion(compatibleVersion == null ? -1 : Byte.parseByte(compatibleVersion)); + + builder.setCompatibleMajorVersion(request.getCompatibleApiVersion().major); return builder; } diff --git a/server/src/main/java/org/elasticsearch/rest/CompatibleConstants.java b/server/src/main/java/org/elasticsearch/rest/CompatibleConstants.java index 2f3f13b893fe3..9719383c3a3ed 100644 --- a/server/src/main/java/org/elasticsearch/rest/CompatibleConstants.java +++ b/server/src/main/java/org/elasticsearch/rest/CompatibleConstants.java @@ -19,8 +19,6 @@ package org.elasticsearch.rest; -import org.elasticsearch.Version; - public class CompatibleConstants { /** @@ -28,7 +26,5 @@ public class CompatibleConstants { */ public static final String COMPATIBLE_ACCEPT_HEADER = "Accept"; public static final String COMPATIBLE_CONTENT_TYPE_HEADER = "Content-Type"; - public static final String COMPATIBLE_PARAMS_KEY = "Compatible-With"; - public static final String COMPATIBLE_VERSION = String.valueOf(Version.minimumRestCompatibilityVersion().major); } 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 e083ea8d770d4..9b50af95d43ea 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; @@ -32,7 +33,6 @@ import org.elasticsearch.common.logging.DeprecationLogger; import org.elasticsearch.common.path.PathTrie; import org.elasticsearch.common.util.concurrent.ThreadContext; -import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.core.internal.io.Streams; @@ -54,8 +54,6 @@ import java.util.stream.Collectors; import static org.elasticsearch.rest.BytesRestResponse.TEXT_CONTENT_TYPE; -import static org.elasticsearch.rest.CompatibleConstants.COMPATIBLE_PARAMS_KEY; -import static org.elasticsearch.rest.CompatibleConstants.COMPATIBLE_VERSION; import static org.elasticsearch.rest.RestStatus.BAD_REQUEST; import static org.elasticsearch.rest.RestStatus.INTERNAL_SERVER_ERROR; import static org.elasticsearch.rest.RestStatus.METHOD_NOT_ALLOWED; @@ -147,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)); } /** @@ -297,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 @@ -309,20 +314,14 @@ 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)) { return; } } else { - if(handler.compatibilityRequired() == false //regular (not removed) handlers are always dispatched - //handlers that were registered compatible, require request to be compatible - || isCompatible(request)) { - dispatchRequest(request, channel, handler); - } else { - handleBadRequest(uri, requestMethod, channel); - } + dispatchRequest(request, channel, handler); return; } } @@ -334,11 +333,6 @@ private void tryAllHandlers(final RestRequest request, final RestChannel channel handleBadRequest(uri, requestMethod, channel); } - private boolean isCompatible(ToXContent.Params params) { - String param = params.param(COMPATIBLE_PARAMS_KEY); - return COMPATIBLE_VERSION.equals(param); - } - Iterator getAllHandlers(@Nullable Map requestParamsRef, String rawPath) { final Supplier> paramsSupplier; if (requestParamsRef == null) { diff --git a/server/src/main/java/org/elasticsearch/rest/RestHandler.java b/server/src/main/java/org/elasticsearch/rest/RestHandler.java index 58c452897308e..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,8 +90,14 @@ default List replacedRoutes() { return Collections.emptyList(); } - default boolean compatibilityRequired(){ - return false; + /** + * 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 { diff --git a/server/src/main/java/org/elasticsearch/rest/RestRequest.java b/server/src/main/java/org/elasticsearch/rest/RestRequest.java index cd52770fd6683..dcfd2f144dbe5 100644 --- a/server/src/main/java/org/elasticsearch/rest/RestRequest.java +++ b/server/src/main/java/org/elasticsearch/rest/RestRequest.java @@ -70,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; @@ -91,6 +91,7 @@ private RestRequest(NamedXContentRegistry xContentRegistry, Map 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) { @@ -110,7 +111,7 @@ private RestRequest(NamedXContentRegistry xContentRegistry, Map this.rawPath = path; this.headers = Collections.unmodifiableMap(headers); this.requestId = requestId; - addCompatibleParameter(headersValidation); + this.compatibleApiVersion = addCompatibleParameter(headersValidation); } protected RestRequest(RestRequest restRequest) { @@ -144,16 +145,14 @@ public static RestRequest request(NamedXContentRegistry xContentRegistry, HttpRe requestIdGenerator.incrementAndGet()); } - private void addCompatibleParameter(boolean headersValidation) { - if(headersValidation){ - if(isRequestingCompatibility()) { - params().put(CompatibleConstants.COMPATIBLE_PARAMS_KEY, - String.valueOf(Version.minimumRestCompatibilityVersion().major)); - //use it so it won't fail request validation with unused parameter - param(CompatibleConstants.COMPATIBLE_PARAMS_KEY); - } + 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); @@ -196,6 +195,15 @@ private boolean isRequestingCompatibility() { 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('?'); diff --git a/server/src/test/java/org/elasticsearch/http/DefaultRestChannelTests.java b/server/src/test/java/org/elasticsearch/http/DefaultRestChannelTests.java index 8d7a026aee034..c3373cfc5840a 100644 --- a/server/src/test/java/org/elasticsearch/http/DefaultRestChannelTests.java +++ b/server/src/test/java/org/elasticsearch/http/DefaultRestChannelTests.java @@ -38,7 +38,6 @@ import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.indices.breaker.NoneCircuitBreakerService; import org.elasticsearch.rest.BytesRestResponse; -import org.elasticsearch.rest.CompatibleConstants; import org.elasticsearch.rest.RestChannel; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.rest.RestResponse; @@ -216,12 +215,12 @@ public void testHeadersSet() { } public void testCompatibleParamIsSet() { - String version = String.valueOf(Version.CURRENT.major-1); + 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=" + version)); + httpRequest.getHeaders().put(HttpHeaders.ACCEPT, List.of("application/vnd.elasticsearch+json;compatible-with=" + majorVersion)); final RestRequest request = RestRequest.request(xContentRegistry(), httpRequest, httpChannel); - assertEquals(version, request.param(CompatibleConstants.COMPATIBLE_PARAMS_KEY)); + assertEquals(Version.fromString(majorVersion+".0.0"), request.getCompatibleApiVersion()); } public void testCookiesSet() { diff --git a/server/src/test/java/org/elasticsearch/rest/CompatibleHeaderCombinationTests.java b/server/src/test/java/org/elasticsearch/rest/CompatibleHeaderCombinationTests.java index e38b7f9efab38..b43e7d10823b5 100644 --- a/server/src/test/java/org/elasticsearch/rest/CompatibleHeaderCombinationTests.java +++ b/server/src/test/java/org/elasticsearch/rest/CompatibleHeaderCombinationTests.java @@ -175,9 +175,8 @@ private Matcher isCompatible() { } private Matcher requestHasVersion(int version) { - return ElasticsearchMatchers.HasPropertyLambdaMatcher.hasProperty(build -> - build.param(CompatibleConstants.COMPATIBLE_PARAMS_KEY) //TODO to be refactored into getVersion - , equalTo(String.valueOf(version))); + return ElasticsearchMatchers.HasPropertyLambdaMatcher.hasProperty(restRequest -> + restRequest.getCompatibleApiVersion(), equalTo(Version.fromString(version + ".0.0"))); } private String bodyNotPresent() { diff --git a/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java b/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java index 4811dbc466623..e4ab71b438113 100644 --- a/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java +++ b/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java @@ -45,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; @@ -128,7 +129,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); @@ -179,7 +180,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 @@ -195,7 +196,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); @@ -220,7 +221,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, @@ -229,6 +230,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); @@ -627,8 +634,8 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c } @Override - public boolean compatibilityRequired() { - return true; + public Version compatibleWithVersion() { + return Version.V_7_0_0; } }); @@ -637,6 +644,22 @@ public boolean compatibilityRequired() { 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) 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 093b5ce28651b..5c09720d8d1d7 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 @@ -183,7 +183,7 @@ public Builder(NamedXContentRegistry xContentRegistry) { } public Builder withHeaders(Map> headers) { - this.headers = headers; + this.headers.putAll(headers); return this; } 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/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 9fbd15dc187a7..f0f80c278c60a 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 @@ -9,6 +9,7 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.ParameterizedMessage; import org.apache.logging.log4j.util.Supplier; +import org.elasticsearch.Version; import org.elasticsearch.action.ActionListener; import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.common.util.concurrent.ThreadContext; @@ -129,7 +130,7 @@ private RestRequest maybeWrapRestRequest(RestRequest restRequest) throws IOExcep } @Override - public boolean compatibilityRequired() { - return restHandler.compatibilityRequired(); + public Version compatibleWithVersion() { + return restHandler.compatibleWithVersion(); } } From 4a2391f35a3446ff696f1d9336499310fcf99c77 Mon Sep 17 00:00:00 2001 From: Przemyslaw Gomulka Date: Fri, 5 Jun 2020 09:18:39 +0200 Subject: [PATCH 09/16] fix compilation error --- .../org/elasticsearch/rest/compat/version7/RestGetActionV7.java | 2 +- .../elasticsearch/rest/compat/version7/RestIndexActionV7.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.java index b0eda49b522c5..063bc9ffe9651 100644 --- a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.java +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.java @@ -50,7 +50,7 @@ public List routes() { @Override public RestChannelConsumer prepareRequest(RestRequest request, final NodeClient client) throws IOException { - deprecationLogger.deprecatedAndMaybeLog("get_with_types", TYPES_DEPRECATION_MESSAGE); + deprecationLogger.deprecate("get_with_types", TYPES_DEPRECATION_MESSAGE); request.param("type"); return super.prepareRequest(request, client); } diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java index 33d9ff76c2cc7..add7728c980b2 100644 --- a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java @@ -42,7 +42,7 @@ public class RestIndexActionV7 { private static final DeprecationLogger deprecationLogger = new DeprecationLogger(LogManager.getLogger(RestIndexAction.class)); private static void logDeprecationMessage() { - deprecationLogger.deprecatedAndMaybeLog("index_with_types", TYPES_DEPRECATION_MESSAGE); + deprecationLogger.deprecate("index_with_types", TYPES_DEPRECATION_MESSAGE); } public static class CompatibleRestIndexAction extends RestIndexAction { From e01df04a9c70c185b27cc6ff930549958d7f85cb Mon Sep 17 00:00:00 2001 From: pgomulka Date: Tue, 16 Jun 2020 15:41:39 +0200 Subject: [PATCH 10/16] temporary fix to data streams to allow compat testing --- .../elasticsearch/test/rest/yaml/ESClientYamlSuiteTestCase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 256de6a6a4df1..def784d4736b4 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 @@ -441,6 +441,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 } } From a0a2f634494154b842f44d91d8fbae8fbe883743 Mon Sep 17 00:00:00 2001 From: pgomulka Date: Tue, 7 Jul 2020 10:52:24 +0200 Subject: [PATCH 11/16] fix after merge master --- .../elasticsearch/rest/compat/version7/RestGetActionV7.java | 3 ++- .../elasticsearch/rest/compat/version7/RestIndexActionV7.java | 2 +- qa/rest-compat-tests/build.gradle | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.java index 063bc9ffe9651..68efa7088bbe7 100644 --- a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.java +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.java @@ -25,6 +25,7 @@ import org.elasticsearch.common.logging.DeprecationLogger; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.rest.action.document.RestGetAction; +import org.elasticsearch.rest.action.document.RestIndexAction; import java.io.IOException; import java.util.List; @@ -34,7 +35,7 @@ public class RestGetActionV7 extends RestGetAction { - private static final DeprecationLogger deprecationLogger = new DeprecationLogger(LogManager.getLogger(RestGetAction.class)); + 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."; diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java index add7728c980b2..1368509a5e72a 100644 --- a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java @@ -39,7 +39,7 @@ 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 = new DeprecationLogger(LogManager.getLogger(RestIndexAction.class)); + private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(RestIndexActionV7.class); private static void logDeprecationMessage() { deprecationLogger.deprecate("index_with_types", TYPES_DEPRECATION_MESSAGE); diff --git a/qa/rest-compat-tests/build.gradle b/qa/rest-compat-tests/build.gradle index 56f48f7d128fc..056058ecf794f 100644 --- a/qa/rest-compat-tests/build.gradle +++ b/qa/rest-compat-tests/build.gradle @@ -18,7 +18,7 @@ task copyRestTestsResources(type: Copy) { integTest.dependsOn(copyRestTestsResources) dependencies { - compile project(':test:framework') + implementation project(':test:framework') } test.enabled = false From 2df6157aa9847fffa8c542a41ea35923f4f4e958 Mon Sep 17 00:00:00 2001 From: Przemyslaw Gomulka Date: Tue, 14 Jul 2020 08:37:06 +0200 Subject: [PATCH 12/16] Compatible logic for Removes typed endpoint from search and related APIs (#54572) This adds a compatible APIs for typed endpoints removed in #41640 202 failing tests These 2 tests are explicitly fixed by this PR CompatRestIT. test {yaml=mtermvectors/21_deprecated_with_types/Deprecated camel case and _ parameters should fail in Term Vectors query} CompatRestIT. test {yaml=mtermvectors/30_mix_typeless_typeful/mtermvectors without types on an index that has types} I think more yml tests should be added to 7.x to cover these endpoints 17th june 1306tests | 197failures | 16ignored | 10m56.91sduration --- .../client/RequestConvertersTests.java | 2 +- .../RestMultiSearchTemplateAction.java | 18 ++- modules/rest-compatibility/build.gradle | 7 +- .../{rest => }/compat/RestCompatPlugin.java | 28 ++++- .../elasticsearch/compat/TypeConsumer.java | 53 +++++++++ .../reindex/RestDeleteByQueryActionV7.java | 59 ++++++++++ .../reindex/RestUpdateByQueryActionV7.java | 60 ++++++++++ .../indices}/RestCreateIndexActionV7.java | 15 ++- .../document}/RestGetActionV7.java | 9 +- .../document}/RestIndexActionV7.java | 8 +- .../RestMultiTermVectorsActionV7.java | 81 +++++++++++++ .../document/RestTermVectorsActionV7.java | 87 ++++++++++++++ .../search/RestMultiSearchActionV7.java | 80 +++++++++++++ .../action/search/RestSearchActionV7.java | 71 +++++++++++ .../RestMultiSearchTemplateActionV7.java | 111 ++++++++++++++++++ .../mustache/RestSearchTemplateActionV7.java | 68 +++++++++++ .../compat/FakeCompatRestRequestBuilder.java | 44 +++++++ .../RestDeleteByQueryActionV7Tests.java | 65 ++++++++++ .../reindex/RestUpdateByQueryActionTests.java | 66 +++++++++++ .../RestCreateIndexActionV7Tests.java | 16 +-- .../document}/RestGetActionV7Tests.java | 25 ++-- .../document}/RestIndexActionV7Tests.java | 14 +-- .../RestMultiTermVectorsActionV7Tests.java | 86 ++++++++++++++ .../RestTermVectorsActionV7Tests.java | 61 ++++++++++ .../search/RestMultiSearchActionV7Tests.java | 64 ++++++++++ .../search/RestSearchActionV7Tests.java | 58 +++++++++ .../RestMultiSearchTemplateActionV7Tests.java | 62 ++++++++++ .../RestSearchTemplateActionV7Tests.java | 58 +++++++++ .../action/search/MultiSearchRequest.java | 8 +- .../termvectors/MultiTermVectorsRequest.java | 8 +- .../termvectors/TermVectorsRequest.java | 10 +- .../action/search/RestMultiSearchAction.java | 27 ++++- .../search/MultiSearchRequestTests.java | 5 +- .../test/rest/FakeRestRequest.java | 5 + 34 files changed, 1361 insertions(+), 78 deletions(-) rename modules/rest-compatibility/src/main/java/org/elasticsearch/{rest => }/compat/RestCompatPlugin.java (63%) create mode 100644 modules/rest-compatibility/src/main/java/org/elasticsearch/compat/TypeConsumer.java create mode 100644 modules/rest-compatibility/src/main/java/org/elasticsearch/index/reindex/RestDeleteByQueryActionV7.java create mode 100644 modules/rest-compatibility/src/main/java/org/elasticsearch/index/reindex/RestUpdateByQueryActionV7.java rename modules/rest-compatibility/src/main/java/org/elasticsearch/rest/{compat/version7 => action/admin/indices}/RestCreateIndexActionV7.java (90%) rename modules/rest-compatibility/src/main/java/org/elasticsearch/rest/{compat/version7 => action/document}/RestGetActionV7.java (87%) rename modules/rest-compatibility/src/main/java/org/elasticsearch/rest/{compat/version7 => action/document}/RestIndexActionV7.java (94%) create mode 100644 modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestMultiTermVectorsActionV7.java create mode 100644 modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestTermVectorsActionV7.java create mode 100644 modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/search/RestMultiSearchActionV7.java create mode 100644 modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/search/RestSearchActionV7.java create mode 100644 modules/rest-compatibility/src/main/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateActionV7.java create mode 100644 modules/rest-compatibility/src/main/java/org/elasticsearch/script/mustache/RestSearchTemplateActionV7.java create mode 100644 modules/rest-compatibility/src/test/java/org/elasticsearch/compat/FakeCompatRestRequestBuilder.java create mode 100644 modules/rest-compatibility/src/test/java/org/elasticsearch/index/reindex/RestDeleteByQueryActionV7Tests.java create mode 100644 modules/rest-compatibility/src/test/java/org/elasticsearch/index/reindex/RestUpdateByQueryActionTests.java rename modules/rest-compatibility/src/test/java/org/elasticsearch/rest/{compat/version7 => action/admin/indices}/RestCreateIndexActionV7Tests.java (79%) rename modules/rest-compatibility/src/test/java/org/elasticsearch/rest/{compat/version7 => action/document}/RestGetActionV7Tests.java (58%) rename modules/rest-compatibility/src/test/java/org/elasticsearch/rest/{compat/version7 => action/document}/RestIndexActionV7Tests.java (76%) create mode 100644 modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestMultiTermVectorsActionV7Tests.java create mode 100644 modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestTermVectorsActionV7Tests.java create mode 100644 modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/search/RestMultiSearchActionV7Tests.java create mode 100644 modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/search/RestSearchActionV7Tests.java create mode 100644 modules/rest-compatibility/src/test/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateActionV7Tests.java create mode 100644 modules/rest-compatibility/src/test/java/org/elasticsearch/script/mustache/RestSearchTemplateActionV7Tests.java 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/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 index 96e81f07d823a..a4b9b20d046a5 100644 --- a/modules/rest-compatibility/build.gradle +++ b/modules/rest-compatibility/build.gradle @@ -19,7 +19,12 @@ esplugin { description 'Adds a compatiblity layer for the prior major versions REST API' - classname 'org.elasticsearch.rest.compat.RestCompatPlugin' + classname 'org.elasticsearch.compat.RestCompatPlugin' +} + +dependencies { + implementation project(':modules:lang-mustache') + implementation project(':modules:reindex') } integTest.enabled = false diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/RestCompatPlugin.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/compat/RestCompatPlugin.java similarity index 63% rename from modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/RestCompatPlugin.java rename to modules/rest-compatibility/src/main/java/org/elasticsearch/compat/RestCompatPlugin.java index 3fe0c356a5a34..f82ee1590c395 100644 --- a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/RestCompatPlugin.java +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/compat/RestCompatPlugin.java @@ -7,7 +7,7 @@ * 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 + * 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 @@ -17,7 +17,7 @@ * under the License. */ -package org.elasticsearch.rest.compat; +package org.elasticsearch.compat; import org.elasticsearch.Version; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; @@ -26,13 +26,21 @@ 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.compat.version7.RestCreateIndexActionV7; -import org.elasticsearch.rest.compat.version7.RestGetActionV7; -import org.elasticsearch.rest.compat.version7.RestIndexActionV7; +import org.elasticsearch.rest.action.admin.indices.RestCreateIndexActionV7; +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.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; @@ -52,11 +60,19 @@ public List getRestHandlers( ) { if (Version.CURRENT.major == 8) { return List.of( + new RestDeleteByQueryActionV7(), + new RestUpdateByQueryActionV7(), + new RestCreateIndexActionV7(), new RestGetActionV7(), new RestIndexActionV7.CompatibleRestIndexAction(), new RestIndexActionV7.CompatibleCreateHandler(), new RestIndexActionV7.CompatibleAutoIdHandler(nodesInCluster), - new RestCreateIndexActionV7() + new RestTermVectorsActionV7(), + new RestMultiTermVectorsActionV7(), + new RestSearchActionV7(), + new RestMultiSearchActionV7(settings), + new RestSearchTemplateActionV7(), + new RestMultiSearchTemplateActionV7(settings) ); } return Collections.emptyList(); 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/compat/version7/RestCreateIndexActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/admin/indices/RestCreateIndexActionV7.java similarity index 90% rename from modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestCreateIndexActionV7.java rename to modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/admin/indices/RestCreateIndexActionV7.java index 6f5d3d1fe272f..463866af9d894 100644 --- a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestCreateIndexActionV7.java +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/admin/indices/RestCreateIndexActionV7.java @@ -7,7 +7,7 @@ * 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 + * 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 @@ -17,7 +17,7 @@ * under the License. */ -package org.elasticsearch.rest.compat.version7; +package org.elasticsearch.rest.action.admin.indices; import org.elasticsearch.Version; import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; @@ -28,7 +28,6 @@ import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.rest.action.RestToXContentListener; -import org.elasticsearch.rest.action.admin.indices.RestCreateIndexAction; import java.io.IOException; import java.util.Collections; @@ -46,7 +45,7 @@ public class RestCreateIndexActionV7 extends RestCreateIndexAction { @Override public String getName() { - return "create_index_action_v7"; + return super.getName() + "_v7"; } @Override @@ -56,19 +55,19 @@ public Version compatibleWithVersion() { @Override public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { - CreateIndexRequest createIndexRequest = prepareRequest(request); + CreateIndexRequest createIndexRequest = prepareV7Request(request); return channel -> client.admin().indices().create(createIndexRequest, new RestToXContentListener<>(channel)); } // default scope for testing - CreateIndexRequest prepareRequest(RestRequest request) { + 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 = prepareMappings(sourceAsMap, request); + sourceAsMap = prepareMappingsV7(sourceAsMap, request); createIndexRequest.source(sourceAsMap, LoggingDeprecationHandler.INSTANCE); } @@ -79,7 +78,7 @@ CreateIndexRequest prepareRequest(RestRequest request) { return createIndexRequest; } - static Map prepareMappings(Map source, RestRequest request) { + static Map prepareMappingsV7(Map source, RestRequest request) { final boolean includeTypeName = request.paramAsBoolean(INCLUDE_TYPE_NAME_PARAMETER, false); @SuppressWarnings("unchecked") diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestGetActionV7.java similarity index 87% rename from modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.java rename to modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestGetActionV7.java index 68efa7088bbe7..a75fc46d2428e 100644 --- a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestGetActionV7.java +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestGetActionV7.java @@ -7,7 +7,7 @@ * 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 + * 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 @@ -17,15 +17,12 @@ * under the License. */ -package org.elasticsearch.rest.compat.version7; +package org.elasticsearch.rest.action.document; -import org.apache.logging.log4j.LogManager; 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.document.RestGetAction; -import org.elasticsearch.rest.action.document.RestIndexAction; import java.io.IOException; import java.util.List; @@ -41,7 +38,7 @@ public class RestGetActionV7 extends RestGetAction { @Override public String getName() { - return "document_get_action_v7"; + return super.getName() + "_v7"; } @Override diff --git a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestIndexActionV7.java similarity index 94% rename from modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java rename to modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestIndexActionV7.java index 1368509a5e72a..134deea9ff9db 100644 --- a/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7.java +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestIndexActionV7.java @@ -7,7 +7,7 @@ * 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 + * 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 @@ -17,15 +17,13 @@ * under the License. */ -package org.elasticsearch.rest.compat.version7; +package org.elasticsearch.rest.action.document; -import org.apache.logging.log4j.LogManager; 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 org.elasticsearch.rest.action.document.RestIndexAction; import java.io.IOException; import java.util.List; @@ -48,7 +46,7 @@ private static void logDeprecationMessage() { public static class CompatibleRestIndexAction extends RestIndexAction { @Override public String getName() { - return "document_index_action_v7"; + return super.getName() + "_v7"; } @Override 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/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/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/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/compat/version7/RestCreateIndexActionV7Tests.java b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/admin/indices/RestCreateIndexActionV7Tests.java similarity index 79% rename from modules/rest-compatibility/src/test/java/org/elasticsearch/rest/compat/version7/RestCreateIndexActionV7Tests.java rename to modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/admin/indices/RestCreateIndexActionV7Tests.java index 781ed2e12e3cc..35140a22b1479 100644 --- a/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/compat/version7/RestCreateIndexActionV7Tests.java +++ b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/admin/indices/RestCreateIndexActionV7Tests.java @@ -7,7 +7,7 @@ * 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 + * 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 @@ -17,28 +17,23 @@ * under the License. */ -package org.elasticsearch.rest.compat.version7; +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.FakeRestRequest; import org.elasticsearch.test.rest.RestActionTestCase; import org.junit.Before; import java.io.IOException; -import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; import static org.hamcrest.Matchers.equalTo; public class RestCreateIndexActionV7Tests extends RestActionTestCase { - String mimeType = "application/vnd.elasticsearch+json;compatible-with=7"; - List contentTypeHeader = Collections.singletonList(mimeType); - RestCreateIndexActionV7 restHandler = new RestCreateIndexActionV7(); @Before @@ -61,14 +56,13 @@ public void testTypeInMapping() throws IOException { Map params = new HashMap<>(); params.put(RestCreateIndexActionV7.INCLUDE_TYPE_NAME_PARAMETER, "true"); - RestRequest request = new FakeRestRequest.Builder(xContentRegistry()).withMethod(RestRequest.Method.PUT) - .withHeaders(Map.of("Content-Type", contentTypeHeader, "Accept", contentTypeHeader)) + RestRequest request = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(RestRequest.Method.PUT) .withPath("/some_index") .withParams(params) .withContent(new BytesArray(content), null) .build(); - CreateIndexRequest createIndexRequest = restHandler.prepareRequest(request); + 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/compat/version7/RestGetActionV7Tests.java b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestGetActionV7Tests.java similarity index 58% rename from modules/rest-compatibility/src/test/java/org/elasticsearch/rest/compat/version7/RestGetActionV7Tests.java rename to modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestGetActionV7Tests.java index 124138ea2a142..725e414fae95a 100644 --- a/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/compat/version7/RestGetActionV7Tests.java +++ b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestGetActionV7Tests.java @@ -17,20 +17,15 @@ * under the License. */ -package org.elasticsearch.rest.compat.version7; +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; -import java.util.Collections; -import java.util.List; -import java.util.Map; - public class RestGetActionV7Tests extends RestActionTestCase { - final String mimeType = "application/vnd.elasticsearch+json;compatible-with=7"; - final List contentTypeHeader = Collections.singletonList(mimeType); @Before public void setUpAction() { @@ -38,18 +33,18 @@ public void setUpAction() { } public void testTypeInPathWithGet() { - FakeRestRequest.Builder deprecatedRequest = new FakeRestRequest.Builder(xContentRegistry()).withHeaders( - Map.of("Content-Type", contentTypeHeader, "Accept", contentTypeHeader) - ).withPath("/some_index/some_type/some_id"); - dispatchRequest(deprecatedRequest.withMethod(RestRequest.Method.GET).build()); + 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.Builder deprecatedRequest = new FakeRestRequest.Builder(xContentRegistry()).withHeaders( - Map.of("Content-Type", contentTypeHeader, "Accept", contentTypeHeader) - ).withPath("/some_index/some_type/some_id"); - dispatchRequest(deprecatedRequest.withMethod(RestRequest.Method.HEAD).build()); + 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/compat/version7/RestIndexActionV7Tests.java b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestIndexActionV7Tests.java similarity index 76% rename from modules/rest-compatibility/src/test/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7Tests.java rename to modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestIndexActionV7Tests.java index 6fb7b2dfac2f2..69812d67333a0 100644 --- a/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/compat/version7/RestIndexActionV7Tests.java +++ b/modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestIndexActionV7Tests.java @@ -17,17 +17,16 @@ * under the License. */ -package org.elasticsearch.rest.compat.version7; +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.FakeRestRequest; import org.elasticsearch.test.rest.RestActionTestCase; import org.junit.Before; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.concurrent.atomic.AtomicReference; public class RestIndexActionV7Tests extends RestActionTestCase { @@ -46,8 +45,7 @@ public void setUpAction() { public void testTypeInPath() { // using CompatibleRestIndexAction - RestRequest deprecatedRequest = new FakeRestRequest.Builder(xContentRegistry()).withMethod(RestRequest.Method.PUT) - .withHeaders(Map.of("Content-Type", contentTypeHeader, "Accept", contentTypeHeader)) + RestRequest deprecatedRequest = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(RestRequest.Method.PUT) .withPath("/some_index/some_type/some_id") .build(); dispatchRequest(deprecatedRequest); @@ -56,8 +54,7 @@ public void testTypeInPath() { public void testCreateWithTypeInPath() { // using CompatibleCreateHandler - RestRequest deprecatedRequest = new FakeRestRequest.Builder(xContentRegistry()).withMethod(RestRequest.Method.PUT) - .withHeaders(Map.of("Content-Type", contentTypeHeader, "Accept", contentTypeHeader)) + RestRequest deprecatedRequest = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(RestRequest.Method.PUT) .withPath("/some_index/some_type/some_id/_create") .build(); dispatchRequest(deprecatedRequest); @@ -66,8 +63,7 @@ public void testCreateWithTypeInPath() { public void testAutoIdWithType() { // using CompatibleAutoIdHandler - RestRequest deprecatedRequest = new FakeRestRequest.Builder(xContentRegistry()).withMethod(RestRequest.Method.POST) - .withHeaders(Map.of("Content-Type", contentTypeHeader, "Accept", contentTypeHeader)) + RestRequest deprecatedRequest = new FakeCompatRestRequestBuilder(xContentRegistry()).withMethod(RestRequest.Method.POST) .withPath("/some_index/some_type/") .build(); dispatchRequest(deprecatedRequest); 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/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/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/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/test/framework/src/main/java/org/elasticsearch/test/rest/FakeRestRequest.java b/test/framework/src/main/java/org/elasticsearch/test/rest/FakeRestRequest.java index fc99063198c87..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,6 +196,11 @@ 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.putAll(headers); return this; From cacef336faa70f97e6f4afa85f2f8185102ca1a8 Mon Sep 17 00:00:00 2001 From: Przemyslaw Gomulka Date: Tue, 14 Jul 2020 13:53:31 +0200 Subject: [PATCH 13/16] Validate compatible handlers have correct version (#58304) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validating if registered compatible rest handlers are overriding compatibleWith method and specify the right compatible version ("197 błędów / +197") --- .../compat/RestCompatPlugin.java | 20 ++++++- .../compat/RestCompatPluginTests.java | 59 +++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 modules/rest-compatibility/src/test/java/org/elasticsearch/compat/RestCompatPluginTests.java 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 index f82ee1590c395..563562debad14 100644 --- a/modules/rest-compatibility/src/main/java/org/elasticsearch/compat/RestCompatPlugin.java +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/compat/RestCompatPlugin.java @@ -44,6 +44,7 @@ import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.function.Supplier; public class RestCompatPlugin extends Plugin implements ActionPlugin { @@ -59,7 +60,8 @@ public List getRestHandlers( Supplier nodesInCluster ) { if (Version.CURRENT.major == 8) { - return List.of( + return validateCompatibleHandlers( + 7, new RestDeleteByQueryActionV7(), new RestUpdateByQueryActionV7(), new RestCreateIndexActionV7(), @@ -77,4 +79,20 @@ public List getRestHandlers( } 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/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 { + + } + }; + } +} From e457a928371044eadf349666baa6a57b506acc85 Mon Sep 17 00:00:00 2001 From: Przemyslaw Gomulka Date: Tue, 14 Jul 2020 14:40:41 +0200 Subject: [PATCH 14/16] Compatible Delete and Update rest actions (#58246) based on compat/search branch fixed tests from delete and update directories. Delete test is still not fixed CompatRestIT. test {yaml=delete/70_mix_typeless_typeful/DELETE with typeless API on an index that has types} current state 1306tests | 174failures previously 1306tests | 197failures relates #54160 --- .../compat/RestCompatPlugin.java | 8 ++- .../action/document/RestDeleteActionV7.java | 62 +++++++++++++++++++ .../action/document/RestUpdateActionV7.java | 62 +++++++++++++++++++ .../document/RestDeleteActionV7Tests.java | 46 ++++++++++++++ .../document/RestUpdateActionV7Tests.java | 46 ++++++++++++++ .../elasticsearch/rest/BaseRestHandler.java | 8 +++ 6 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestDeleteActionV7.java create mode 100644 modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/document/RestUpdateActionV7.java create mode 100644 modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestDeleteActionV7Tests.java create mode 100644 modules/rest-compatibility/src/test/java/org/elasticsearch/rest/action/document/RestUpdateActionV7Tests.java 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 index 563562debad14..e846c925166d4 100644 --- a/modules/rest-compatibility/src/main/java/org/elasticsearch/compat/RestCompatPlugin.java +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/compat/RestCompatPlugin.java @@ -7,7 +7,7 @@ * 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 + * 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 @@ -33,10 +33,12 @@ 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; @@ -74,7 +76,9 @@ public List getRestHandlers( new RestSearchActionV7(), new RestMultiSearchActionV7(settings), new RestSearchTemplateActionV7(), - new RestMultiSearchTemplateActionV7(settings) + new RestMultiSearchTemplateActionV7(settings), + new RestDeleteActionV7(), + new RestUpdateActionV7() ); } return Collections.emptyList(); 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/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/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/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/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. From b09bd5ae5b9242cfc2669fb7ea75cc067ee0136a Mon Sep 17 00:00:00 2001 From: Przemyslaw Gomulka Date: Tue, 14 Jul 2020 16:45:34 +0200 Subject: [PATCH 15/16] Mappings get put and get mappings (#58426) based on pgomulka:compat/delete_update #58246 CompatRestIT. test {yaml=indices.get_field_mapping/30_missing_type/Raise 404 when type doesn't exist} because of that change (custom type to _doc) a test that was expecting 404 for a type that did not exist, passes now as the logic is trying to fetch for _doc (any type that exist on that index will be good). CompatRestIT. test {yaml=indices.get_mapping/11_basic_with_types/Get /{index}/_mapping with empty mappings} this is because I can't tell if the mapping contained a type or not when fetching a mapping. CompatRestIT. test {yaml=indices.get_mapping/20_missing_type/Existent and non-existent type returns 404 and the existing type} CompatRestIT. test {yaml=indices.get_mapping/20_missing_type/Existent and non-existent types returns 404 and the existing type} CompatRestIT. test {yaml=indices.get_mapping/20_missing_type/No type matching pattern returns 404} CompatRestIT. test {yaml=indices.get_mapping/20_missing_type/Non-existent type returns 404} failing - (not returning 404) - because type is always found (defaulted to _doc) CompatRestIT. test {yaml=indices.get_mapping/20_missing_type/Type missing when no types exist} CompatRestIT. test {yaml=indices.get_mapping/61_empty_with_types/Check empty mapping when getting all mappings via /_mapping} CompatRestIT. test {yaml=indices.get_template/11_basic_with_types/Get template with no mappings} CompatRestIT. test {yaml=indices.get_template/11_basic_with_types/Get template} CompatRestIT. test {yaml=indices.put_mapping/20_mix_typeless_typeful/PUT mapping with _doc on an index that has types} CompatRestIT. test {yaml=indices.put_mapping/20_mix_typeless_typeful/PUT mapping with typeless API on an index that has types} #47364 #41676 --- .../indices/RestCreateIndexActionV7.java | 1 + .../indices/RestGetFieldMappingActionV7.java | 88 ++++++ .../admin/indices/RestGetMappingActionV7.java | 86 ++++++ .../admin/indices/RestPutMappingActionV7.java | 111 ++++++++ qa/rest-compat-tests/build.gradle | 2 + .../11_basic_with_types.yml | 85 ++++++ .../21_missing_field_with_types.yml | 23 ++ .../51_field_wildcards_with_types.yml | 181 ++++++++++++ .../11_basic_with_types.yml | 175 ++++++++++++ .../11_basic_with_types.yml | 79 ++++++ .../all_path_options_with_types.yml | 263 ++++++++++++++++++ .../mapping/get/GetFieldMappingsResponse.java | 11 +- .../mapping/get/GetMappingsResponse.java | 12 +- .../admin/indices/RestPutMappingAction.java | 7 +- .../yaml/section/MatchAssertionTests.java | 10 + 15 files changed, 1130 insertions(+), 4 deletions(-) create mode 100644 modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetFieldMappingActionV7.java create mode 100644 modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetMappingActionV7.java create mode 100644 modules/rest-compatibility/src/main/java/org/elasticsearch/rest/action/admin/indices/RestPutMappingActionV7.java create mode 100644 qa/rest-compat-tests/src/test/resources/rest-api-spec/test/indices.get_field_mapping/11_basic_with_types.yml create mode 100644 qa/rest-compat-tests/src/test/resources/rest-api-spec/test/indices.get_field_mapping/21_missing_field_with_types.yml create mode 100644 qa/rest-compat-tests/src/test/resources/rest-api-spec/test/indices.get_field_mapping/51_field_wildcards_with_types.yml create mode 100644 qa/rest-compat-tests/src/test/resources/rest-api-spec/test/indices.get_mapping/11_basic_with_types.yml create mode 100644 qa/rest-compat-tests/src/test/resources/rest-api-spec/test/indices.put_mapping/11_basic_with_types.yml create mode 100644 qa/rest-compat-tests/src/test/resources/rest-api-spec/test/indices.put_mapping/all_path_options_with_types.yml 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 index 463866af9d894..26861e0b5cd1c 100644 --- 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 @@ -42,6 +42,7 @@ 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() { 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/qa/rest-compat-tests/build.gradle b/qa/rest-compat-tests/build.gradle index 056058ecf794f..78fac721fa843 100644 --- a/qa/rest-compat-tests/build.gradle +++ b/qa/rest-compat-tests/build.gradle @@ -15,6 +15,8 @@ task copyRestTestsResources(type: Copy) { dependsOn ':distribution:bwc:minor:checkoutBwcBranch' } +//copyOverrides.dependsOn(copyRestTestsResources) +processTestResources.dependsOn(copyRestTestsResources) integTest.dependsOn(copyRestTestsResources) dependencies { 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/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/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/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")); + } } From 40783d59166190fc62d366f823d35aee44f46470 Mon Sep 17 00:00:00 2001 From: pgomulka Date: Wed, 15 Jul 2020 15:39:21 +0200 Subject: [PATCH 16/16] draft --- .../compat/RestCompatPlugin.java | 8 +++- x-pack/plugin/rest-compat/build.gradle | 18 +++++++++ .../compat/CompatRequestWrapper.java | 19 ++++++++++ .../compat/RestCompatRequestPlugin.java | 37 +++++++++++++++++++ 4 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugin/rest-compat/build.gradle create mode 100644 x-pack/plugin/rest-compat/src/main/java/org/elasticsearch/compat/CompatRequestWrapper.java create mode 100644 x-pack/plugin/rest-compat/src/main/java/org/elasticsearch/compat/RestCompatRequestPlugin.java 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 index e846c925166d4..d45d60cff2a52 100644 --- a/modules/rest-compatibility/src/main/java/org/elasticsearch/compat/RestCompatPlugin.java +++ b/modules/rest-compatibility/src/main/java/org/elasticsearch/compat/RestCompatPlugin.java @@ -19,6 +19,8 @@ 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; @@ -50,7 +52,7 @@ import java.util.function.Supplier; public class RestCompatPlugin extends Plugin implements ActionPlugin { - + Logger log = LogManager.getLogger(RestCompatPlugin.class); @Override public List getRestHandlers( Settings settings, @@ -61,7 +63,9 @@ public List getRestHandlers( IndexNameExpressionResolver indexNameExpressionResolver, Supplier nodesInCluster ) { - if (Version.CURRENT.major == 8) { + boolean compatibilityEnabled = Boolean.parseBoolean(settings.get("compat.setting")); + log.warn(" compat setting "+compatibilityEnabled); + if (compatibilityEnabled && Version.CURRENT.major == 8) { return validateCompatibleHandlers( 7, new RestDeleteByQueryActionV7(), 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(); + } + +}