|
@@ -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;
|
|
@@ -47,6 +48,7 @@ import org.elasticsearch.test.rest.FakeRestRequest;
|
|
|
import org.elasticsearch.usage.UsageService;
|
|
|
import org.junit.After;
|
|
|
import org.junit.Before;
|
|
|
+import org.mockito.Mockito;
|
|
|
|
|
|
import java.io.IOException;
|
|
|
import java.util.Arrays;
|
|
@@ -60,6 +62,7 @@ import java.util.Set;
|
|
|
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;
|
|
@@ -97,7 +100,8 @@ public class RestControllerTests extends ESTestCase {
|
|
|
|
|
|
HttpServerTransport httpServerTransport = new TestHttpServerTransport();
|
|
|
client = new NoOpNodeClient(this.getTestName());
|
|
|
- restController = new RestController(Collections.emptySet(), null, client, circuitBreakerService, usageService);
|
|
|
+ restController = new RestController(Collections.emptySet(), null, client, circuitBreakerService, usageService,
|
|
|
+ CompatibleVersion.CURRENT_VERSION);
|
|
|
restController.registerHandler(RestRequest.Method.GET, "/",
|
|
|
(request, channel, client) -> channel.sendResponse(
|
|
|
new BytesRestResponse(RestStatus.OK, BytesRestResponse.TEXT_CONTENT_TYPE, BytesArray.EMPTY)));
|
|
@@ -117,7 +121,8 @@ public class RestControllerTests extends ESTestCase {
|
|
|
final ThreadContext threadContext = client.threadPool().getThreadContext();
|
|
|
Set<RestHeaderDefinition> headers = new HashSet<>(Arrays.asList(new RestHeaderDefinition("header.1", true),
|
|
|
new RestHeaderDefinition("header.2", true)));
|
|
|
- final RestController restController = new RestController(headers, null, null, circuitBreakerService, usageService);
|
|
|
+ final RestController restController = new RestController(headers, null, null, circuitBreakerService, usageService,
|
|
|
+ CompatibleVersion.CURRENT_VERSION);
|
|
|
Map<String, List<String>> restHeaders = new HashMap<>();
|
|
|
restHeaders.put("header.1", Collections.singletonList("true"));
|
|
|
restHeaders.put("header.2", Collections.singletonList("true"));
|
|
@@ -153,7 +158,8 @@ public class RestControllerTests extends ESTestCase {
|
|
|
final ThreadContext threadContext = client.threadPool().getThreadContext();
|
|
|
Set<RestHeaderDefinition> headers = new HashSet<>(Arrays.asList(new RestHeaderDefinition("header.1", true),
|
|
|
new RestHeaderDefinition("header.2", false)));
|
|
|
- final RestController restController = new RestController(headers, null, null, circuitBreakerService, usageService);
|
|
|
+ final RestController restController = new RestController(headers, null, null, circuitBreakerService, usageService,
|
|
|
+ CompatibleVersion.CURRENT_VERSION);
|
|
|
Map<String, List<String>> restHeaders = new HashMap<>();
|
|
|
restHeaders.put("header.1", Collections.singletonList("boo"));
|
|
|
restHeaders.put("header.2", List.of("foo", "bar"));
|
|
@@ -167,7 +173,8 @@ public class RestControllerTests extends ESTestCase {
|
|
|
final ThreadContext threadContext = client.threadPool().getThreadContext();
|
|
|
Set<RestHeaderDefinition> headers = new HashSet<>(Arrays.asList(new RestHeaderDefinition("header.1", true),
|
|
|
new RestHeaderDefinition("header.2", false)));
|
|
|
- final RestController restController = new RestController(headers, null, client, circuitBreakerService, usageService);
|
|
|
+ final RestController restController = new RestController(headers, null, client, circuitBreakerService, usageService,
|
|
|
+ CompatibleVersion.CURRENT_VERSION);
|
|
|
Map<String, List<String>> restHeaders = new HashMap<>();
|
|
|
restHeaders.put("header.1", Collections.singletonList("boo"));
|
|
|
restHeaders.put("header.2", List.of("foo", "foo"));
|
|
@@ -188,7 +195,7 @@ public class RestControllerTests extends ESTestCase {
|
|
|
|
|
|
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
|
|
@@ -204,7 +211,7 @@ public class RestControllerTests extends ESTestCase {
|
|
|
|
|
|
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);
|
|
|
|
|
@@ -221,7 +228,8 @@ public class RestControllerTests extends ESTestCase {
|
|
|
}
|
|
|
|
|
|
public void testRegisterSecondMethodWithDifferentNamedWildcard() {
|
|
|
- final RestController restController = new RestController(null, null, null, circuitBreakerService, usageService);
|
|
|
+ final RestController restController = new RestController(null, null, null, circuitBreakerService, usageService,
|
|
|
+ CompatibleVersion.CURRENT_VERSION);
|
|
|
|
|
|
RestRequest.Method firstMethod = randomFrom(RestRequest.Method.values());
|
|
|
RestRequest.Method secondMethod =
|
|
@@ -229,7 +237,8 @@ public class RestControllerTests extends ESTestCase {
|
|
|
|
|
|
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,
|
|
@@ -238,6 +247,12 @@ public class RestControllerTests extends ESTestCase {
|
|
|
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);
|
|
@@ -248,7 +263,7 @@ public class RestControllerTests extends ESTestCase {
|
|
|
h -> {
|
|
|
assertSame(handler, h);
|
|
|
return (RestRequest request, RestChannel channel, NodeClient client) -> wrapperCalled.set(true);
|
|
|
- }, client, circuitBreakerService, usageService);
|
|
|
+ }, client, circuitBreakerService, usageService, CompatibleVersion.CURRENT_VERSION);
|
|
|
restController.registerHandler(RestRequest.Method.GET, "/wrapped", handler);
|
|
|
RestRequest request = testRestRequest("/wrapped", "{}", XContentType.JSON);
|
|
|
AssertingChannel channel = new AssertingChannel(request, true, RestStatus.BAD_REQUEST);
|
|
@@ -311,7 +326,8 @@ public class RestControllerTests extends ESTestCase {
|
|
|
String content = randomAlphaOfLength((int) Math.round(BREAKER_LIMIT.getBytes() / inFlightRequestsBreaker.getOverhead()));
|
|
|
RestRequest request = testRestRequest("/", content, null);
|
|
|
AssertingChannel channel = new AssertingChannel(request, true, RestStatus.NOT_ACCEPTABLE);
|
|
|
- restController = new RestController(Collections.emptySet(), null, null, circuitBreakerService, usageService);
|
|
|
+ restController = new RestController(Collections.emptySet(), null, null, circuitBreakerService, usageService,
|
|
|
+ CompatibleVersion.CURRENT_VERSION);
|
|
|
restController.registerHandler(RestRequest.Method.GET, "/",
|
|
|
(r, c, client) -> c.sendResponse(
|
|
|
new BytesRestResponse(RestStatus.OK, BytesRestResponse.TEXT_CONTENT_TYPE, BytesArray.EMPTY)));
|
|
@@ -620,6 +636,106 @@ public class RestControllerTests extends ESTestCase {
|
|
|
assertThat(channel.getRestResponse().getHeaders().get("Allow"), hasItem(equalTo(RestRequest.Method.GET.toString())));
|
|
|
}
|
|
|
|
|
|
+ public void testDispatchCompatibleHandler() {
|
|
|
+
|
|
|
+ RestController restController = new RestController(Collections.emptySet(), null, client, circuitBreakerService, usageService,
|
|
|
+ (a,c,h)->Version.CURRENT.minimumRestCompatibilityVersion());//always return compatible version
|
|
|
+
|
|
|
+ final byte version = Version.CURRENT.minimumRestCompatibilityVersion().major;
|
|
|
+
|
|
|
+ final String mimeType = randomCompatibleMimeType(version);
|
|
|
+ String content = randomAlphaOfLength((int) Math.round(BREAKER_LIMIT.getBytes() / inFlightRequestsBreaker.getOverhead()));
|
|
|
+ final List<String> mimeTypeList = Collections.singletonList(mimeType);
|
|
|
+ FakeRestRequest fakeRestRequest = new FakeRestRequest.Builder(NamedXContentRegistry.EMPTY)
|
|
|
+ .withContent(new BytesArray(content), RestRequest.parseContentType(mimeTypeList))
|
|
|
+ .withPath("/foo")
|
|
|
+ .withHeaders(Map.of("Content-Type", mimeTypeList, "Accept", mimeTypeList))
|
|
|
+ .build();
|
|
|
+ AssertingChannel channel = new AssertingChannel(fakeRestRequest, true, RestStatus.OK);
|
|
|
+ // dispatch to a compatible handler
|
|
|
+ restController.registerHandler(RestRequest.Method.GET, "/foo", new RestHandler() {
|
|
|
+ @Override
|
|
|
+ public void handleRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception {
|
|
|
+ XContentBuilder xContentBuilder = channel.newBuilder();
|
|
|
+ assertThat(xContentBuilder.getCompatibleMajorVersion(), equalTo(version));
|
|
|
+ channel.sendResponse(new BytesRestResponse(RestStatus.OK, BytesRestResponse.TEXT_CONTENT_TYPE, BytesArray.EMPTY));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Version compatibleWithVersion() {
|
|
|
+ return Version.CURRENT.minimumRestCompatibilityVersion();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ assertFalse(channel.getSendResponseCalled());
|
|
|
+ restController.dispatchRequest(fakeRestRequest, channel, new ThreadContext(Settings.EMPTY));
|
|
|
+ assertTrue(channel.getSendResponseCalled());
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testDispatchCompatibleRequestToNewlyAddedHandler() {
|
|
|
+
|
|
|
+ RestController restController = new RestController(Collections.emptySet(), null, client, circuitBreakerService, usageService,
|
|
|
+ (a,c,h)->Version.CURRENT.minimumRestCompatibilityVersion());//always return compatible version
|
|
|
+
|
|
|
+ final byte version = Version.CURRENT.minimumRestCompatibilityVersion().major;
|
|
|
+
|
|
|
+ final String mimeType = randomCompatibleMimeType(version);
|
|
|
+ String content = randomAlphaOfLength((int) Math.round(BREAKER_LIMIT.getBytes() / inFlightRequestsBreaker.getOverhead()));
|
|
|
+ final List<String> mimeTypeList = Collections.singletonList(mimeType);
|
|
|
+ FakeRestRequest fakeRestRequest = new FakeRestRequest.Builder(NamedXContentRegistry.EMPTY)
|
|
|
+ .withContent(new BytesArray(content), RestRequest.parseContentType(mimeTypeList))
|
|
|
+ .withPath("/foo")
|
|
|
+ .withHeaders(Map.of("Content-Type", mimeTypeList, "Accept", mimeTypeList))
|
|
|
+ .build();
|
|
|
+ AssertingChannel channel = new AssertingChannel(fakeRestRequest, true, RestStatus.OK);
|
|
|
+
|
|
|
+ // dispatch to a CURRENT newly added handler
|
|
|
+ restController.registerHandler(RestRequest.Method.GET, "/foo", new RestHandler() {
|
|
|
+ @Override
|
|
|
+ public void handleRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception {
|
|
|
+ XContentBuilder xContentBuilder = channel.newBuilder();
|
|
|
+ // even though the handler is CURRENT, the xContentBuilder has the version requested by a client.
|
|
|
+ // This allows to implement the compatible logic within the serialisation without introducing V7 (compatible) handler
|
|
|
+ // when only response shape has changed
|
|
|
+ assertThat(xContentBuilder.getCompatibleMajorVersion(), equalTo(version));
|
|
|
+ channel.sendResponse(new BytesRestResponse(RestStatus.OK, BytesRestResponse.TEXT_CONTENT_TYPE, BytesArray.EMPTY));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Version compatibleWithVersion() {
|
|
|
+ return Version.CURRENT;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ assertFalse(channel.getSendResponseCalled());
|
|
|
+ restController.dispatchRequest(fakeRestRequest, channel, new ThreadContext(Settings.EMPTY));
|
|
|
+ assertTrue(channel.getSendResponseCalled());
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testRegisterIncompatibleVersionHandler() {
|
|
|
+ //using restController which uses a compatible version function returning always Version.CURRENT
|
|
|
+ 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::mediaTypeWithoutParameters)
|
|
|
+ .toArray(String[]::new))
|
|
|
+ .split("/")[1];
|
|
|
+ return randomFrom("application/vnd.elasticsearch+" + subtype + ";compatible-with=" + version);
|
|
|
+ }
|
|
|
|
|
|
private static final class TestHttpServerTransport extends AbstractLifecycleComponent implements
|
|
|
HttpServerTransport {
|