/* * 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.ElasticsearchException; import org.elasticsearch.ElasticsearchStatusException; import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.ResourceAlreadyExistsException; import org.elasticsearch.ResourceNotFoundException; import org.elasticsearch.action.search.SearchPhaseExecutionException; import org.elasticsearch.action.search.ShardSearchFailure; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.Index; import org.elasticsearch.search.SearchShardTarget; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.rest.FakeRestRequest; import org.elasticsearch.transport.RemoteTransportException; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Collections; import java.util.Map; import static org.elasticsearch.ElasticsearchExceptionTests.assertDeepEquals; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; public class BytesRestResponseTests extends ESTestCase { class UnknownException extends Exception { UnknownException(final String message, final Throwable cause) { super(message, cause); } } public void testWithHeaders() throws Exception { RestRequest request = new FakeRestRequest(); RestChannel channel = randomBoolean() ? new DetailedExceptionRestChannel(request) : new SimpleExceptionRestChannel(request); BytesRestResponse response = new BytesRestResponse(channel, new WithHeadersException()); assertEquals(2, response.getHeaders().size()); assertThat(response.getHeaders().get("n1"), notNullValue()); assertThat(response.getHeaders().get("n1"), contains("v11", "v12")); assertThat(response.getHeaders().get("n2"), notNullValue()); assertThat(response.getHeaders().get("n2"), contains("v21", "v22")); } public void testSimpleExceptionMessage() throws Exception { RestRequest request = new FakeRestRequest(); RestChannel channel = new SimpleExceptionRestChannel(request); Exception t = new ElasticsearchException("an error occurred reading data", new FileNotFoundException("/foo/bar")); BytesRestResponse response = new BytesRestResponse(channel, t); String text = response.content().utf8ToString(); assertThat(text, containsString("ElasticsearchException[an error occurred reading data]")); assertThat(text, not(containsString("FileNotFoundException"))); assertThat(text, not(containsString("/foo/bar"))); assertThat(text, not(containsString("error_trace"))); } public void testDetailedExceptionMessage() throws Exception { RestRequest request = new FakeRestRequest(); RestChannel channel = new DetailedExceptionRestChannel(request); Exception t = new ElasticsearchException("an error occurred reading data", new FileNotFoundException("/foo/bar")); BytesRestResponse response = new BytesRestResponse(channel, t); String text = response.content().utf8ToString(); assertThat(text, containsString("{\"type\":\"exception\",\"reason\":\"an error occurred reading data\"}")); assertThat(text, containsString("{\"type\":\"file_not_found_exception\",\"reason\":\"/foo/bar\"}")); } public void testNonElasticsearchExceptionIsNotShownAsSimpleMessage() throws Exception { RestRequest request = new FakeRestRequest(); RestChannel channel = new SimpleExceptionRestChannel(request); Exception t = new UnknownException("an error occurred reading data", new FileNotFoundException("/foo/bar")); BytesRestResponse response = new BytesRestResponse(channel, t); String text = response.content().utf8ToString(); assertThat(text, not(containsString("UnknownException[an error occurred reading data]"))); assertThat(text, not(containsString("FileNotFoundException[/foo/bar]"))); assertThat(text, not(containsString("error_trace"))); assertThat(text, containsString("\"error\":\"No ElasticsearchException found\"")); } public void testErrorTrace() throws Exception { RestRequest request = new FakeRestRequest(); request.params().put("error_trace", "true"); RestChannel channel = new DetailedExceptionRestChannel(request); Exception t = new UnknownException("an error occurred reading data", new FileNotFoundException("/foo/bar")); BytesRestResponse response = new BytesRestResponse(channel, t); String text = response.content().utf8ToString(); assertThat(text, containsString("\"type\":\"unknown_exception\",\"reason\":\"an error occurred reading data\"")); assertThat(text, containsString("{\"type\":\"file_not_found_exception\"")); assertThat(text, containsString("\"stack_trace\":\"[an error occurred reading data]")); } public void testGuessRootCause() throws IOException { RestRequest request = new FakeRestRequest(); RestChannel channel = new DetailedExceptionRestChannel(request); { Exception e = new ElasticsearchException("an error occurred reading data", new FileNotFoundException("/foo/bar")); BytesRestResponse response = new BytesRestResponse(channel, e); String text = response.content().utf8ToString(); assertThat(text, containsString("{\"root_cause\":[{\"type\":\"exception\",\"reason\":\"an error occurred reading data\"}]")); } { Exception e = new FileNotFoundException("/foo/bar"); BytesRestResponse response = new BytesRestResponse(channel, e); String text = response.content().utf8ToString(); assertThat(text, containsString("{\"root_cause\":[{\"type\":\"file_not_found_exception\",\"reason\":\"/foo/bar\"}]")); } } public void testNullThrowable() throws Exception { RestRequest request = new FakeRestRequest(); RestChannel channel = new SimpleExceptionRestChannel(request); BytesRestResponse response = new BytesRestResponse(channel, null); String text = response.content().utf8ToString(); assertThat(text, containsString("\"error\":\"unknown\"")); assertThat(text, not(containsString("error_trace"))); } public void testConvert() throws IOException { RestRequest request = new FakeRestRequest(); RestChannel channel = new DetailedExceptionRestChannel(request); ShardSearchFailure failure = new ShardSearchFailure(new ParsingException(1, 2, "foobar", null), new SearchShardTarget("node_1", new Index("foo", "_na_"), 1)); ShardSearchFailure failure1 = new ShardSearchFailure(new ParsingException(1, 2, "foobar", null), new SearchShardTarget("node_1", new Index("foo", "_na_"), 2)); SearchPhaseExecutionException ex = new SearchPhaseExecutionException("search", "all shards failed", new ShardSearchFailure[] {failure, failure1}); BytesRestResponse response = new BytesRestResponse(channel, new RemoteTransportException("foo", ex)); String text = response.content().utf8ToString(); String expected = "{\"error\":{\"root_cause\":[{\"type\":\"parsing_exception\",\"reason\":\"foobar\",\"line\":1,\"col\":2}],\"type\":\"search_phase_execution_exception\",\"reason\":\"all shards failed\",\"phase\":\"search\",\"grouped\":true,\"failed_shards\":[{\"shard\":1,\"index\":\"foo\",\"node\":\"node_1\",\"reason\":{\"type\":\"parsing_exception\",\"reason\":\"foobar\",\"line\":1,\"col\":2}}]},\"status\":400}"; assertEquals(expected.trim(), text.trim()); String stackTrace = ExceptionsHelper.stackTrace(ex); assertTrue(stackTrace.contains("Caused by: ParsingException[foobar]")); } public void testResponseWhenPathContainsEncodingError() throws IOException { final String path = "%a"; final RestRequest request = new RestRequest(NamedXContentRegistry.EMPTY, Collections.emptyMap(), path, Collections.emptyMap()) { @Override public Method method() { return null; } @Override public String uri() { return null; } @Override public boolean hasContent() { return false; } @Override public BytesReference content() { return null; } }; final IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> RestUtils.decodeComponent(request.rawPath())); final RestChannel channel = new DetailedExceptionRestChannel(request); // if we try to decode the path, this will throw an IllegalArgumentException again final BytesRestResponse response = new BytesRestResponse(channel, e); assertNotNull(response.content()); final String content = response.content().utf8ToString(); assertThat(content, containsString("\"type\":\"illegal_argument_exception\"")); assertThat(content, containsString("\"reason\":\"partial escape sequence at end of string: %a\"")); assertThat(content, containsString("\"status\":" + 400)); } public void testResponseWhenInternalServerError() throws IOException { final RestRequest request = new FakeRestRequest(); final RestChannel channel = new DetailedExceptionRestChannel(request); final BytesRestResponse response = new BytesRestResponse(channel, new ElasticsearchException("simulated")); assertNotNull(response.content()); final String content = response.content().utf8ToString(); assertThat(content, containsString("\"type\":\"exception\"")); assertThat(content, containsString("\"reason\":\"simulated\"")); assertThat(content, containsString("\"status\":" + 500)); } public void testErrorToAndFromXContent() throws IOException { final boolean detailed = randomBoolean(); Exception original; ElasticsearchException cause = null; String reason; String type = "exception"; RestStatus status = RestStatus.INTERNAL_SERVER_ERROR; boolean addHeadersOrMetadata = false; switch (randomIntBetween(0, 5)) { case 0: original = new ElasticsearchException("ElasticsearchException without cause"); if (detailed) { addHeadersOrMetadata = randomBoolean(); reason = "ElasticsearchException without cause"; } else { reason = "ElasticsearchException[ElasticsearchException without cause]"; } break; case 1: original = new ElasticsearchException("ElasticsearchException with a cause", new FileNotFoundException("missing")); if (detailed) { addHeadersOrMetadata = randomBoolean(); type = "exception"; reason = "ElasticsearchException with a cause"; cause = new ElasticsearchException("Elasticsearch exception [type=file_not_found_exception, reason=missing]"); } else { reason = "ElasticsearchException[ElasticsearchException with a cause]"; } break; case 2: original = new ResourceNotFoundException("ElasticsearchException with custom status"); status = RestStatus.NOT_FOUND; if (detailed) { addHeadersOrMetadata = randomBoolean(); type = "resource_not_found_exception"; reason = "ElasticsearchException with custom status"; } else { reason = "ResourceNotFoundException[ElasticsearchException with custom status]"; } break; case 3: TransportAddress address = buildNewFakeTransportAddress(); original = new RemoteTransportException("remote", address, "action", new ResourceAlreadyExistsException("ElasticsearchWrapperException with a cause that has a custom status")); status = RestStatus.BAD_REQUEST; if (detailed) { type = "resource_already_exists_exception"; reason = "ElasticsearchWrapperException with a cause that has a custom status"; } else { reason = "RemoteTransportException[[remote][" + address.toString() + "][action]]"; } break; case 4: original = new RemoteTransportException("ElasticsearchWrapperException with a cause that has a special treatment", new IllegalArgumentException("wrong")); status = RestStatus.BAD_REQUEST; if (detailed) { type = "illegal_argument_exception"; reason = "wrong"; } else { reason = "RemoteTransportException[[ElasticsearchWrapperException with a cause that has a special treatment]]"; } break; case 5: status = randomFrom(RestStatus.values()); original = new ElasticsearchStatusException("ElasticsearchStatusException with random status", status); if (detailed) { addHeadersOrMetadata = randomBoolean(); type = "status_exception"; reason = "ElasticsearchStatusException with random status"; } else { reason = "ElasticsearchStatusException[ElasticsearchStatusException with random status]"; } break; default: throw new UnsupportedOperationException("Failed to generate random exception"); } String message = "Elasticsearch exception [type=" + type + ", reason=" + reason + "]"; ElasticsearchStatusException expected = new ElasticsearchStatusException(message, status, cause); if (addHeadersOrMetadata) { ElasticsearchException originalException = ((ElasticsearchException) original); if (randomBoolean()) { originalException.addHeader("foo", "bar", "baz"); expected.addHeader("foo", "bar", "baz"); } if (randomBoolean()) { originalException.addMetadata("es.metadata_0", "0"); expected.addMetadata("es.metadata_0", "0"); } if (randomBoolean()) { String resourceType = randomAlphaOfLength(5); String resourceId = randomAlphaOfLength(5); originalException.setResources(resourceType, resourceId); expected.setResources(resourceType, resourceId); } if (randomBoolean()) { originalException.setIndex("_index"); expected.setIndex("_index"); } } final XContentType xContentType = randomFrom(XContentType.values()); Map<String, String> params = Collections.singletonMap("format", xContentType.mediaType()); RestRequest request = new FakeRestRequest.Builder(xContentRegistry()).withParams(params).build(); RestChannel channel = detailed ? new DetailedExceptionRestChannel(request) : new SimpleExceptionRestChannel(request); BytesRestResponse response = new BytesRestResponse(channel, original); ElasticsearchException parsedError; try (XContentParser parser = createParser(xContentType.xContent(), response.content())) { parsedError = BytesRestResponse.errorFromXContent(parser); assertNull(parser.nextToken()); } assertEquals(expected.status(), parsedError.status()); assertDeepEquals(expected, parsedError); } public void testNoErrorFromXContent() throws IOException { IllegalStateException e = expectThrows(IllegalStateException.class, () -> { try (XContentBuilder builder = XContentBuilder.builder(randomFrom(XContentType.values()).xContent())) { builder.startObject(); builder.field("status", randomFrom(RestStatus.values()).getStatus()); builder.endObject(); try (XContentParser parser = createParser(builder.contentType().xContent(), builder.bytes())) { BytesRestResponse.errorFromXContent(parser); } } }); assertEquals("Failed to parse elasticsearch status exception: no exception was found", e.getMessage()); } public static class WithHeadersException extends ElasticsearchException { WithHeadersException() { super(""); this.addHeader("n1", "v11", "v12"); this.addHeader("n2", "v21", "v22"); this.addMetadata("es.test", "value1", "value2"); } } private static class SimpleExceptionRestChannel extends AbstractRestChannel { SimpleExceptionRestChannel(RestRequest request) { super(request, false); } @Override public void sendResponse(RestResponse response) { } } private static class DetailedExceptionRestChannel extends AbstractRestChannel { DetailedExceptionRestChannel(RestRequest request) { super(request, true); } @Override public void sendResponse(RestResponse response) { } } }