/* * 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; import org.apache.lucene.util.Constants; import org.elasticsearch.action.NoShardAvailableActionException; import org.elasticsearch.action.OriginalIndices; import org.elasticsearch.action.RoutingMissingException; import org.elasticsearch.action.search.SearchPhaseExecutionException; import org.elasticsearch.action.search.ShardSearchFailure; import org.elasticsearch.action.support.broadcast.BroadcastShardOperationFailedException; import org.elasticsearch.client.transport.NoNodeAvailableException; import org.elasticsearch.cluster.block.ClusterBlockException; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.Strings; import org.elasticsearch.common.breaker.CircuitBreakingException; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentLocation; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.discovery.DiscoverySettings; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.index.query.QueryShardException; import org.elasticsearch.index.shard.IndexShardRecoveringException; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.node.NodeClosedException; import org.elasticsearch.repositories.RepositoryException; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.script.ScriptException; import org.elasticsearch.search.SearchContextMissingException; import org.elasticsearch.search.SearchParseException; import org.elasticsearch.search.SearchShardTarget; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.TestSearchContext; import org.elasticsearch.transport.RemoteTransportException; import java.io.EOFException; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.FileAlreadyExistsException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import static java.util.Collections.emptyList; import static java.util.Collections.singleton; import static java.util.Collections.singletonList; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.CoreMatchers.hasItems; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.startsWith; public class ElasticsearchExceptionTests extends ESTestCase { public void testStatus() { ElasticsearchException exception = new ElasticsearchException("test"); assertThat(exception.status(), equalTo(RestStatus.INTERNAL_SERVER_ERROR)); exception = new ElasticsearchException("test", new RuntimeException()); assertThat(exception.status(), equalTo(RestStatus.INTERNAL_SERVER_ERROR)); exception = new ElasticsearchException("test", new ResourceNotFoundException("test")); assertThat(exception.status(), equalTo(RestStatus.INTERNAL_SERVER_ERROR)); exception = new RemoteTransportException("test", new ResourceNotFoundException("test")); assertThat(exception.status(), equalTo(RestStatus.NOT_FOUND)); exception = new RemoteTransportException("test", new ResourceAlreadyExistsException("test")); assertThat(exception.status(), equalTo(RestStatus.BAD_REQUEST)); exception = new RemoteTransportException("test", new IllegalArgumentException("foobar")); assertThat(exception.status(), equalTo(RestStatus.BAD_REQUEST)); exception = new RemoteTransportException("test", new IllegalStateException("foobar")); assertThat(exception.status(), equalTo(RestStatus.INTERNAL_SERVER_ERROR)); } public void testGuessRootCause() { { ElasticsearchException exception = new ElasticsearchException("foo", new ElasticsearchException("bar", new IndexNotFoundException("foo", new RuntimeException("foobar")))); ElasticsearchException[] rootCauses = exception.guessRootCauses(); assertEquals(rootCauses.length, 1); assertEquals(ElasticsearchException.getExceptionName(rootCauses[0]), "index_not_found_exception"); assertEquals(rootCauses[0].getMessage(), "no such index"); 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}); if (randomBoolean()) { rootCauses = (randomBoolean() ? new RemoteTransportException("remoteboom", ex) : ex).guessRootCauses(); } else { rootCauses = ElasticsearchException.guessRootCauses(randomBoolean() ? new RemoteTransportException("remoteboom", ex) : ex); } assertEquals(ElasticsearchException.getExceptionName(rootCauses[0]), "parsing_exception"); assertEquals(rootCauses[0].getMessage(), "foobar"); ElasticsearchException oneLevel = new ElasticsearchException("foo", new RuntimeException("foobar")); rootCauses = oneLevel.guessRootCauses(); assertEquals(ElasticsearchException.getExceptionName(rootCauses[0]), "exception"); assertEquals(rootCauses[0].getMessage(), "foo"); } { ShardSearchFailure failure = new ShardSearchFailure( new ParsingException(1, 2, "foobar", null), new SearchShardTarget("node_1", new Index("foo", "_na_"), 1)); ShardSearchFailure failure1 = new ShardSearchFailure(new QueryShardException(new Index("foo1", "_na_"), "foobar", null), new SearchShardTarget("node_1", new Index("foo1", "_na_"), 1)); ShardSearchFailure failure2 = new ShardSearchFailure(new QueryShardException(new Index("foo1", "_na_"), "foobar", null), new SearchShardTarget("node_1", new Index("foo1", "_na_"), 2)); SearchPhaseExecutionException ex = new SearchPhaseExecutionException("search", "all shards failed", new ShardSearchFailure[]{failure, failure1, failure2}); final ElasticsearchException[] rootCauses = ex.guessRootCauses(); assertEquals(rootCauses.length, 2); assertEquals(ElasticsearchException.getExceptionName(rootCauses[0]), "parsing_exception"); assertEquals(rootCauses[0].getMessage(), "foobar"); assertEquals(((ParsingException) rootCauses[0]).getLineNumber(), 1); assertEquals(((ParsingException) rootCauses[0]).getColumnNumber(), 2); assertEquals(ElasticsearchException.getExceptionName(rootCauses[1]), "query_shard_exception"); assertEquals((rootCauses[1]).getIndex().getName(), "foo1"); assertEquals(rootCauses[1].getMessage(), "foobar"); } { final ElasticsearchException[] foobars = ElasticsearchException.guessRootCauses(new IllegalArgumentException("foobar")); assertEquals(foobars.length, 1); assertTrue(foobars[0] instanceof ElasticsearchException); assertEquals(foobars[0].getMessage(), "foobar"); assertEquals(foobars[0].getCause().getClass(), IllegalArgumentException.class); assertEquals(foobars[0].getExceptionName(), "illegal_argument_exception"); } } public void testDeduplicate() throws IOException { { 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", randomBoolean() ? failure1.getCause() : failure.getCause(), new ShardSearchFailure[]{failure, failure1}); XContentBuilder builder = XContentFactory.jsonBuilder(); builder.startObject(); ex.toXContent(builder, ToXContent.EMPTY_PARAMS); builder.endObject(); String expected = "{\"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}}]}"; assertEquals(expected, builder.string()); } { ShardSearchFailure failure = new ShardSearchFailure(new ParsingException(1, 2, "foobar", null), new SearchShardTarget("node_1", new Index("foo", "_na_"), 1)); ShardSearchFailure failure1 = new ShardSearchFailure(new QueryShardException(new Index("foo1", "_na_"), "foobar", null), new SearchShardTarget("node_1", new Index("foo1", "_na_"), 1)); ShardSearchFailure failure2 = new ShardSearchFailure(new QueryShardException(new Index("foo1", "_na_"), "foobar", null), new SearchShardTarget("node_1", new Index("foo1", "_na_"), 2)); SearchPhaseExecutionException ex = new SearchPhaseExecutionException("search", "all shards failed", new ShardSearchFailure[]{failure, failure1, failure2}); XContentBuilder builder = XContentFactory.jsonBuilder(); builder.startObject(); ex.toXContent(builder, ToXContent.EMPTY_PARAMS); builder.endObject(); String expected = "{\"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}},{\"shard\":1," + "\"index\":\"foo1\",\"node\":\"node_1\",\"reason\":{\"type\":\"query_shard_exception\",\"reason\":\"foobar\"," + "\"index_uuid\":\"_na_\",\"index\":\"foo1\"}}]}"; assertEquals(expected, builder.string()); } { 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)); NullPointerException nullPointerException = new NullPointerException(); SearchPhaseExecutionException ex = new SearchPhaseExecutionException("search", "all shards failed", nullPointerException, new ShardSearchFailure[]{failure, failure1}); assertEquals(nullPointerException, ex.getCause()); XContentBuilder builder = XContentFactory.jsonBuilder(); builder.startObject(); ex.toXContent(builder, ToXContent.EMPTY_PARAMS); builder.endObject(); String expected = "{\"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}}]," + "\"caused_by\":{\"type\":\"null_pointer_exception\",\"reason\":null}}"; assertEquals(expected, builder.string()); } } /** * Check whether this exception contains an exception of the given type: * either it is of the given class itself or it contains a nested cause * of the given type. * * @param exType the exception type to look for * @return whether there is a nested exception of the specified type */ private static boolean contains(Throwable t, Class<? extends Throwable> exType) { if (exType == null) { return false; } for (Throwable cause = t; t != null; t = t.getCause()) { if (exType.isInstance(cause)) { return true; } } return false; } public void testGetRootCause() { Exception root = new RuntimeException("foobar"); ElasticsearchException exception = new ElasticsearchException("foo", new ElasticsearchException("bar", new IllegalArgumentException("index is closed", root))); assertEquals(root, exception.getRootCause()); assertTrue(contains(exception, RuntimeException.class)); assertFalse(contains(exception, EOFException.class)); } public void testToString() { ElasticsearchException exception = new ElasticsearchException("foo", new ElasticsearchException("bar", new IllegalArgumentException("index is closed", new RuntimeException("foobar")))); assertEquals("ElasticsearchException[foo]; nested: ElasticsearchException[bar]; nested: IllegalArgumentException" + "[index is closed]; nested: RuntimeException[foobar];", exception.toString()); } public void testToXContent() throws IOException { { ElasticsearchException e = new ElasticsearchException("test"); assertExceptionAsJson(e, "{\"type\":\"exception\",\"reason\":\"test\"}"); } { ElasticsearchException e = new IndexShardRecoveringException(new ShardId("_test", "_0", 5)); assertExceptionAsJson(e, "{\"type\":\"index_shard_recovering_exception\"," + "\"reason\":\"CurrentState[RECOVERING] Already recovering\",\"index_uuid\":\"_0\"," + "\"shard\":\"5\",\"index\":\"_test\"}"); } { ElasticsearchException e = new BroadcastShardOperationFailedException(new ShardId("_index", "_uuid", 12), "foo", new IllegalStateException("bar")); assertExceptionAsJson(e, "{\"type\":\"illegal_state_exception\",\"reason\":\"bar\"}"); } { ElasticsearchException e = new ElasticsearchException(new IllegalArgumentException("foo")); assertExceptionAsJson(e, "{\"type\":\"exception\",\"reason\":\"java.lang.IllegalArgumentException: foo\"," + "\"caused_by\":{\"type\":\"illegal_argument_exception\",\"reason\":\"foo\"}}"); } { ElasticsearchException e = new SearchParseException(new TestSearchContext(null), "foo", new XContentLocation(1,0)); assertExceptionAsJson(e, "{\"type\":\"search_parse_exception\",\"reason\":\"foo\",\"line\":1,\"col\":0}"); } { ElasticsearchException ex = new ElasticsearchException("foo", new ElasticsearchException("bar", new IllegalArgumentException("index is closed", new RuntimeException("foobar")))); assertExceptionAsJson(ex, "{\"type\":\"exception\",\"reason\":\"foo\",\"caused_by\":{\"type\":\"exception\"," + "\"reason\":\"bar\",\"caused_by\":{\"type\":\"illegal_argument_exception\",\"reason\":\"index is closed\"," + "\"caused_by\":{\"type\":\"runtime_exception\",\"reason\":\"foobar\"}}}}"); } { ElasticsearchException e = new ElasticsearchException("foo", new IllegalStateException("bar")); assertExceptionAsJson(e, "{\"type\":\"exception\",\"reason\":\"foo\"," + "\"caused_by\":{\"type\":\"illegal_state_exception\",\"reason\":\"bar\"}}"); // Test the same exception but with the "rest.exception.stacktrace.skip" parameter disabled: the stack_trace must be present // in the JSON. Since the stack can be large, it only checks the beginning of the JSON. ToXContent.Params params = new ToXContent.MapParams( Collections.singletonMap(ElasticsearchException.REST_EXCEPTION_SKIP_STACK_TRACE, "false")); String actual; try (XContentBuilder builder = XContentBuilder.builder(XContentType.JSON.xContent())) { builder.startObject(); e.toXContent(builder, params); builder.endObject(); actual = builder.string(); } assertThat(actual, startsWith("{\"type\":\"exception\",\"reason\":\"foo\"," + "\"caused_by\":{\"type\":\"illegal_state_exception\",\"reason\":\"bar\"," + "\"stack_trace\":\"java.lang.IllegalStateException: bar" + (Constants.WINDOWS ? "\\r\\n" : "\\n") + "\\tat org.elasticsearch.")); } } public void testGenerateThrowableToXContent() throws IOException { { Exception ex; if (randomBoolean()) { // just a wrapper which is omitted ex = new RemoteTransportException("foobar", new FileNotFoundException("foo not found")); } else { ex = new FileNotFoundException("foo not found"); } assertExceptionAsJson(ex, "{\"type\":\"file_not_found_exception\",\"reason\":\"foo not found\"}"); } { ParsingException ex = new ParsingException(1, 2, "foobar", null); assertExceptionAsJson(ex, "{\"type\":\"parsing_exception\",\"reason\":\"foobar\",\"line\":1,\"col\":2}"); } { // test equivalence ElasticsearchException ex = new RemoteTransportException("foobar", new FileNotFoundException("foo not found")); String toXContentString = Strings.toString(ex); String throwableString = Strings.toString((builder, params) -> { ElasticsearchException.generateThrowableXContent(builder, params, ex); return builder; }); assertEquals(throwableString, toXContentString); assertEquals("{\"type\":\"file_not_found_exception\",\"reason\":\"foo not found\"}", toXContentString); } { // render header and metadata ParsingException ex = new ParsingException(1, 2, "foobar", null); ex.addMetadata("es.test1", "value1"); ex.addMetadata("es.test2", "value2"); ex.addHeader("test", "some value"); ex.addHeader("test_multi", "some value", "another value"); String expected = "{\"type\":\"parsing_exception\",\"reason\":\"foobar\",\"line\":1,\"col\":2," + "\"test1\":\"value1\",\"test2\":\"value2\"," + "\"header\":{\"test_multi\":" + "[\"some value\",\"another value\"],\"test\":\"some value\"}}"; assertExceptionAsJson(ex, expected); } } public void testToXContentWithHeadersAndMetadata() throws IOException { ElasticsearchException e = new ElasticsearchException("foo", new ElasticsearchException("bar", new ElasticsearchException("baz", new ClusterBlockException(singleton(DiscoverySettings.NO_MASTER_BLOCK_WRITES))))); e.addHeader("foo_0", "0"); e.addHeader("foo_1", "1"); e.addMetadata("es.metadata_foo_0", "foo_0"); e.addMetadata("es.metadata_foo_1", "foo_1"); final String expectedJson = "{" + "\"type\":\"exception\"," + "\"reason\":\"foo\"," + "\"metadata_foo_0\":\"foo_0\"," + "\"metadata_foo_1\":\"foo_1\"," + "\"caused_by\":{" + "\"type\":\"exception\"," + "\"reason\":\"bar\"," + "\"caused_by\":{" + "\"type\":\"exception\"," + "\"reason\":\"baz\"," + "\"caused_by\":{" + "\"type\":\"cluster_block_exception\"," + "\"reason\":\"blocked by: [SERVICE_UNAVAILABLE/2/no master];\"" + "}" + "}" + "}," + "\"header\":{" + "\"foo_0\":\"0\"," + "\"foo_1\":\"1\"" + "}" + "}"; assertExceptionAsJson(e, expectedJson); ElasticsearchException parsed; try (XContentParser parser = createParser(XContentType.JSON.xContent(), expectedJson)) { assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); parsed = ElasticsearchException.fromXContent(parser); assertEquals(XContentParser.Token.END_OBJECT, parser.currentToken()); assertNull(parser.nextToken()); } assertNotNull(parsed); assertEquals(parsed.getMessage(), "Elasticsearch exception [type=exception, reason=foo]"); assertThat(parsed.getHeaderKeys(), hasSize(2)); assertEquals(parsed.getHeader("foo_0").get(0), "0"); assertEquals(parsed.getHeader("foo_1").get(0), "1"); assertThat(parsed.getMetadataKeys(), hasSize(2)); assertEquals(parsed.getMetadata("es.metadata_foo_0").get(0), "foo_0"); assertEquals(parsed.getMetadata("es.metadata_foo_1").get(0), "foo_1"); ElasticsearchException cause = (ElasticsearchException) parsed.getCause(); assertEquals(cause.getMessage(), "Elasticsearch exception [type=exception, reason=bar]"); cause = (ElasticsearchException) cause.getCause(); assertEquals(cause.getMessage(), "Elasticsearch exception [type=exception, reason=baz]"); cause = (ElasticsearchException) cause.getCause(); assertEquals(cause.getMessage(), "Elasticsearch exception [type=cluster_block_exception, reason=blocked by: [SERVICE_UNAVAILABLE/2/no master];]"); } public void testFromXContent() throws IOException { final XContent xContent = randomFrom(XContentType.values()).xContent(); XContentBuilder builder = XContentBuilder.builder(xContent) .startObject() .field("type", "foo") .field("reason", "something went wrong") .field("stack_trace", "...") .endObject(); builder = shuffleXContent(builder); ElasticsearchException parsed; try (XContentParser parser = createParser(xContent, builder.bytes())) { assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); parsed = ElasticsearchException.fromXContent(parser); assertEquals(XContentParser.Token.END_OBJECT, parser.currentToken()); assertNull(parser.nextToken()); } assertNotNull(parsed); assertEquals(parsed.getMessage(), "Elasticsearch exception [type=foo, reason=something went wrong, stack_trace=...]"); } public void testFromXContentWithCause() throws IOException { ElasticsearchException e = new ElasticsearchException("foo", new ElasticsearchException("bar", new ElasticsearchException("baz", new RoutingMissingException("_test", "_type", "_id")))); final XContent xContent = randomFrom(XContentType.values()).xContent(); XContentBuilder builder = XContentBuilder.builder(xContent).startObject().value(e).endObject(); builder = shuffleXContent(builder); ElasticsearchException parsed; try (XContentParser parser = createParser(builder)) { assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); parsed = ElasticsearchException.fromXContent(parser); assertEquals(XContentParser.Token.END_OBJECT, parser.currentToken()); assertNull(parser.nextToken()); } assertNotNull(parsed); assertEquals(parsed.getMessage(), "Elasticsearch exception [type=exception, reason=foo]"); ElasticsearchException cause = (ElasticsearchException) parsed.getCause(); assertEquals(cause.getMessage(), "Elasticsearch exception [type=exception, reason=bar]"); cause = (ElasticsearchException) cause.getCause(); assertEquals(cause.getMessage(), "Elasticsearch exception [type=exception, reason=baz]"); cause = (ElasticsearchException) cause.getCause(); assertEquals(cause.getMessage(), "Elasticsearch exception [type=routing_missing_exception, reason=routing is required for [_test]/[_type]/[_id]]"); assertThat(cause.getHeaderKeys(), hasSize(0)); assertThat(cause.getMetadataKeys(), hasSize(2)); assertThat(cause.getMetadata("es.index"), hasItem("_test")); assertThat(cause.getMetadata("es.index_uuid"), hasItem("_na_")); } public void testFromXContentWithHeadersAndMetadata() throws IOException { RoutingMissingException routing = new RoutingMissingException("_test", "_type", "_id"); ElasticsearchException baz = new ElasticsearchException("baz", routing); baz.addHeader("baz_0", "baz0"); baz.addMetadata("es.baz_1", "baz1"); baz.addHeader("baz_2", "baz2"); baz.addMetadata("es.baz_3", "baz3"); ElasticsearchException bar = new ElasticsearchException("bar", baz); bar.addMetadata("es.bar_0", "bar0"); bar.addHeader("bar_1", "bar1"); bar.addMetadata("es.bar_2", "bar2"); ElasticsearchException foo = new ElasticsearchException("foo", bar); foo.addMetadata("es.foo_0", "foo0"); foo.addHeader("foo_1", "foo1"); final XContent xContent = randomFrom(XContentType.values()).xContent(); XContentBuilder builder = XContentBuilder.builder(xContent).startObject().value(foo).endObject(); builder = shuffleXContent(builder); ElasticsearchException parsed; try (XContentParser parser = createParser(builder)) { assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); parsed = ElasticsearchException.fromXContent(parser); assertEquals(XContentParser.Token.END_OBJECT, parser.currentToken()); assertNull(parser.nextToken()); } assertNotNull(parsed); assertEquals(parsed.getMessage(), "Elasticsearch exception [type=exception, reason=foo]"); assertThat(parsed.getHeaderKeys(), hasSize(1)); assertThat(parsed.getHeader("foo_1"), hasItem("foo1")); assertThat(parsed.getMetadataKeys(), hasSize(1)); assertThat(parsed.getMetadata("es.foo_0"), hasItem("foo0")); ElasticsearchException cause = (ElasticsearchException) parsed.getCause(); assertEquals(cause.getMessage(), "Elasticsearch exception [type=exception, reason=bar]"); assertThat(cause.getHeaderKeys(), hasSize(1)); assertThat(cause.getHeader("bar_1"), hasItem("bar1")); assertThat(cause.getMetadataKeys(), hasSize(2)); assertThat(cause.getMetadata("es.bar_0"), hasItem("bar0")); assertThat(cause.getMetadata("es.bar_2"), hasItem("bar2")); cause = (ElasticsearchException) cause.getCause(); assertEquals(cause.getMessage(), "Elasticsearch exception [type=exception, reason=baz]"); assertThat(cause.getHeaderKeys(), hasSize(2)); assertThat(cause.getHeader("baz_0"), hasItem("baz0")); assertThat(cause.getHeader("baz_2"), hasItem("baz2")); assertThat(cause.getMetadataKeys(), hasSize(2)); assertThat(cause.getMetadata("es.baz_1"), hasItem("baz1")); assertThat(cause.getMetadata("es.baz_3"), hasItem("baz3")); cause = (ElasticsearchException) cause.getCause(); assertEquals(cause.getMessage(), "Elasticsearch exception [type=routing_missing_exception, reason=routing is required for [_test]/[_type]/[_id]]"); assertThat(cause.getHeaderKeys(), hasSize(0)); assertThat(cause.getMetadataKeys(), hasSize(2)); assertThat(cause.getMetadata("es.index"), hasItem("_test")); assertThat(cause.getMetadata("es.index_uuid"), hasItem("_na_")); } /** * Test that some values like arrays of numbers are ignored when parsing back * an exception. */ public void testFromXContentWithIgnoredMetadataAndHeaders() throws IOException { final XContent xContent = randomFrom(XContentType.values()).xContent(); // The exception content to parse is built using a XContentBuilder // because the current Java API does not allow to add metadata/headers // of other types than list of strings. BytesReference originalBytes; try (XContentBuilder builder = XContentBuilder.builder(xContent)) { builder.startObject() .field("metadata_int", 1) .array("metadata_array_of_ints", new int[]{8, 13, 21}) .field("reason", "Custom reason") .array("metadata_array_of_boolean", new boolean[]{false, false}) .startArray("metadata_array_of_objects") .startObject() .field("object_array_one", "value_one") .endObject() .startObject() .field("object_array_two", "value_two") .endObject() .endArray() .field("type", "custom_exception") .field("metadata_long", 1L) .array("metadata_array_of_longs", new long[]{2L, 3L, 5L}) .field("metadata_other", "some metadata") .startObject("header") .field("header_string", "some header") .array("header_array_of_strings", new String[]{"foo", "bar", "baz"}) .endObject() .startObject("metadata_object") .field("object_field", "value") .endObject() .endObject(); try (XContentBuilder shuffledBuilder = shuffleXContent(builder)) { originalBytes = shuffledBuilder.bytes(); } } ElasticsearchException parsedException; try (XContentParser parser = createParser(xContent, originalBytes)) { assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); parsedException = ElasticsearchException.fromXContent(parser); assertEquals(XContentParser.Token.END_OBJECT, parser.currentToken()); assertNull(parser.nextToken()); } assertNotNull(parsedException); assertEquals("Elasticsearch exception [type=custom_exception, reason=Custom reason]", parsedException.getMessage()); assertEquals(2, parsedException.getHeaderKeys().size()); assertThat(parsedException.getHeader("header_string"), hasItem("some header")); assertThat(parsedException.getHeader("header_array_of_strings"), hasItems("foo", "bar", "baz")); assertEquals(1, parsedException.getMetadataKeys().size()); assertThat(parsedException.getMetadata("es.metadata_other"), hasItem("some metadata")); } public void testThrowableToAndFromXContent() throws IOException { final XContent xContent = randomFrom(XContentType.values()).xContent(); final Tuple<Throwable, ElasticsearchException> exceptions = randomExceptions(); final Throwable throwable = exceptions.v1(); BytesReference throwableBytes = toShuffledXContent((builder, params) -> { ElasticsearchException.generateThrowableXContent(builder, params, throwable); return builder; }, xContent.type(), ToXContent.EMPTY_PARAMS, randomBoolean()); ElasticsearchException parsedException; try (XContentParser parser = createParser(xContent, throwableBytes)) { assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); parsedException = ElasticsearchException.fromXContent(parser); assertEquals(XContentParser.Token.END_OBJECT, parser.currentToken()); assertNull(parser.nextToken()); } assertDeepEquals(exceptions.v2(), parsedException); } public void testUnknownFailureToAndFromXContent() throws IOException { final XContent xContent = randomFrom(XContentType.values()).xContent(); BytesReference failureBytes = toShuffledXContent((builder, params) -> { // Prints a null failure using generateFailureXContent() ElasticsearchException.generateFailureXContent(builder, params, null, randomBoolean()); return builder; }, xContent.type(), ToXContent.EMPTY_PARAMS, randomBoolean()); ElasticsearchException parsedFailure; try (XContentParser parser = createParser(xContent, failureBytes)) { assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken()); parsedFailure = ElasticsearchException.failureFromXContent(parser); assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken()); assertNull(parser.nextToken()); } // Failure was null, expecting a "unknown" reason assertEquals("Elasticsearch exception [type=exception, reason=unknown]", parsedFailure.getMessage()); assertEquals(0, parsedFailure.getHeaders().size()); assertEquals(0, parsedFailure.getMetadata().size()); } public void testFailureToAndFromXContentWithNoDetails() throws IOException { final XContent xContent = randomFrom(XContentType.values()).xContent(); final Exception failure = (Exception) randomExceptions().v1(); BytesReference failureBytes = toShuffledXContent((builder, params) -> { ElasticsearchException.generateFailureXContent(builder, params, failure, false); return builder; }, xContent.type(), ToXContent.EMPTY_PARAMS, randomBoolean()); try (XContentParser parser = createParser(xContent, failureBytes)) { failureBytes = shuffleXContent(parser, randomBoolean()).bytes(); } ElasticsearchException parsedFailure; try (XContentParser parser = createParser(xContent, failureBytes)) { assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken()); parsedFailure = ElasticsearchException.failureFromXContent(parser); assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken()); assertNull(parser.nextToken()); } assertNotNull(parsedFailure); String reason; if (failure instanceof ElasticsearchException) { reason = failure.getClass().getSimpleName() + "[" + failure.getMessage() + "]"; } else { reason = "No ElasticsearchException found"; } assertEquals(ElasticsearchException.buildMessage("exception", reason, null), parsedFailure.getMessage()); assertEquals(0, parsedFailure.getHeaders().size()); assertEquals(0, parsedFailure.getMetadata().size()); assertNull(parsedFailure.getCause()); } public void testFailureToAndFromXContentWithDetails() throws IOException { final XContent xContent = randomFrom(XContentType.values()).xContent(); Exception failure; Throwable failureCause; ElasticsearchException expected; ElasticsearchException expectedCause; ElasticsearchException suppressed; switch (randomIntBetween(0, 6)) { case 0: // Simple elasticsearch exception without cause failure = new NoNodeAvailableException("A"); expected = new ElasticsearchException("Elasticsearch exception [type=no_node_available_exception, reason=A]"); expected.addSuppressed(new ElasticsearchException("Elasticsearch exception [type=no_node_available_exception, reason=A]")); break; case 1: // Simple elasticsearch exception with headers (other metadata of type number are not parsed) failure = new CircuitBreakingException("B", 5_000, 2_000); ((ElasticsearchException) failure).addHeader("header_name", "0", "1"); expected = new ElasticsearchException("Elasticsearch exception [type=circuit_breaking_exception, reason=B]"); expected.addHeader("header_name", "0", "1"); suppressed = new ElasticsearchException("Elasticsearch exception [type=circuit_breaking_exception, reason=B]"); suppressed.addHeader("header_name", "0", "1"); expected.addSuppressed(suppressed); break; case 2: // Elasticsearch exception with a cause, headers and parsable metadata failureCause = new NullPointerException("var is null"); failure = new ScriptException("C", failureCause, singletonList("stack"), "test", "painless"); ((ElasticsearchException) failure).addHeader("script_name", "my_script"); expectedCause = new ElasticsearchException("Elasticsearch exception [type=null_pointer_exception, reason=var is null]"); expected = new ElasticsearchException("Elasticsearch exception [type=script_exception, reason=C]", expectedCause); expected.addHeader("script_name", "my_script"); expected.addMetadata("es.lang", "painless"); expected.addMetadata("es.script", "test"); expected.addMetadata("es.script_stack", "stack"); suppressed = new ElasticsearchException("Elasticsearch exception [type=script_exception, reason=C]"); suppressed.addHeader("script_name", "my_script"); suppressed.addMetadata("es.lang", "painless"); suppressed.addMetadata("es.script", "test"); suppressed.addMetadata("es.script_stack", "stack"); expected.addSuppressed(suppressed); break; case 3: // JDK exception without cause failure = new IllegalStateException("D"); expected = new ElasticsearchException("Elasticsearch exception [type=illegal_state_exception, reason=D]"); suppressed = new ElasticsearchException("Elasticsearch exception [type=illegal_state_exception, reason=D]"); expected.addSuppressed(suppressed); break; case 4: // JDK exception with cause failureCause = new RoutingMissingException("idx", "type", "id"); failure = new RuntimeException("E", failureCause); expectedCause = new ElasticsearchException("Elasticsearch exception [type=routing_missing_exception, " + "reason=routing is required for [idx]/[type]/[id]]"); expectedCause.addMetadata("es.index", "idx"); expectedCause.addMetadata("es.index_uuid", "_na_"); expected = new ElasticsearchException("Elasticsearch exception [type=runtime_exception, reason=E]", expectedCause); suppressed = new ElasticsearchException("Elasticsearch exception [type=runtime_exception, reason=E]"); expected.addSuppressed(suppressed); break; case 5: // Wrapped exception with cause failureCause = new FileAlreadyExistsException("File exists"); failure = new BroadcastShardOperationFailedException(new ShardId("_index", "_uuid", 5), "F", failureCause); expected = new ElasticsearchException("Elasticsearch exception [type=file_already_exists_exception, reason=File exists]"); // strangely, the wrapped exception appears as the root cause... suppressed = new ElasticsearchException("Elasticsearch exception [type=broadcast_shard_operation_failed_exception, " + "reason=F]"); expected.addSuppressed(suppressed); break; case 6: // SearchPhaseExecutionException with cause and multiple failures DiscoveryNode node = new DiscoveryNode("node_g", buildNewFakeTransportAddress(), Version.CURRENT); failureCause = new NodeClosedException(node); failureCause = new NoShardAvailableActionException(new ShardId("_index_g", "_uuid_g", 6), "node_g", failureCause); ShardSearchFailure[] shardFailures = new ShardSearchFailure[]{ new ShardSearchFailure(new ParsingException(0, 0, "Parsing g", null), new SearchShardTarget("node_g", new ShardId(new Index("_index_g", "_uuid_g"), 61), null, OriginalIndices.NONE)), new ShardSearchFailure(new RepositoryException("repository_g", "Repo"), new SearchShardTarget("node_g", new ShardId(new Index("_index_g", "_uuid_g"), 62), null, OriginalIndices.NONE)), new ShardSearchFailure(new SearchContextMissingException(0L), null) }; failure = new SearchPhaseExecutionException("phase_g", "G", failureCause, shardFailures); expectedCause = new ElasticsearchException("Elasticsearch exception [type=node_closed_exception, " + "reason=node closed " + node + "]"); expectedCause = new ElasticsearchException("Elasticsearch exception [type=no_shard_available_action_exception, " + "reason=node_g]", expectedCause); expectedCause.addMetadata("es.index", "_index_g"); expectedCause.addMetadata("es.index_uuid", "_uuid_g"); expectedCause.addMetadata("es.shard", "6"); expected = new ElasticsearchException("Elasticsearch exception [type=search_phase_execution_exception, " + "reason=G]", expectedCause); expected.addMetadata("es.phase", "phase_g"); expected.addSuppressed(new ElasticsearchException("Elasticsearch exception [type=parsing_exception, reason=Parsing g]")); expected.addSuppressed(new ElasticsearchException("Elasticsearch exception [type=repository_exception, " + "reason=[repository_g] Repo]")); expected.addSuppressed(new ElasticsearchException("Elasticsearch exception [type=search_context_missing_exception, " + "reason=No search context found for id [0]]")); break; default: throw new UnsupportedOperationException("Failed to generate randomized failure"); } Exception finalFailure = failure; BytesReference failureBytes = toShuffledXContent((builder, params) -> { ElasticsearchException.generateFailureXContent(builder, params, finalFailure, true); return builder; }, xContent.type(), ToXContent.EMPTY_PARAMS, randomBoolean()); try (XContentParser parser = createParser(xContent, failureBytes)) { failureBytes = shuffleXContent(parser, randomBoolean()).bytes(); } ElasticsearchException parsedFailure; try (XContentParser parser = createParser(xContent, failureBytes)) { assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken()); parsedFailure = ElasticsearchException.failureFromXContent(parser); assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken()); assertNull(parser.nextToken()); } assertDeepEquals(expected, parsedFailure); } /** * Builds a {@link ToXContent} using a JSON XContentBuilder and compares the result to the given json in string format. * * By default, the stack trace of the exception is not rendered. The parameter `errorTrace` forces the stack trace to * be rendered like the REST API does when the "error_trace" parameter is set to true. */ private static void assertToXContentAsJson(ToXContent e, String expectedJson) throws IOException { BytesReference actual = XContentHelper.toXContent(e, XContentType.JSON, randomBoolean()); assertToXContentEquivalent(new BytesArray(expectedJson), actual, XContentType.JSON); } private static void assertExceptionAsJson(Exception e, String expectedJson) throws IOException { assertToXContentAsJson((builder, params) -> { ElasticsearchException.generateThrowableXContent(builder, params, e); return builder; }, expectedJson); } public static void assertDeepEquals(ElasticsearchException expected, ElasticsearchException actual) { do { if (expected == null) { assertNull(actual); } else { assertNotNull(actual); } assertEquals(expected.getMessage(), actual.getMessage()); assertEquals(expected.getHeaders(), actual.getHeaders()); assertEquals(expected.getMetadata(), actual.getMetadata()); assertEquals(expected.getResourceType(), actual.getResourceType()); assertEquals(expected.getResourceId(), actual.getResourceId()); Throwable[] expectedSuppressed = expected.getSuppressed(); Throwable[] actualSuppressed = actual.getSuppressed(); if (expectedSuppressed == null) { assertNull(actualSuppressed); } else { assertNotNull(actualSuppressed); assertEquals(expectedSuppressed.length, actualSuppressed.length); for (int i = 0; i < expectedSuppressed.length; i++) { assertDeepEquals((ElasticsearchException) expectedSuppressed[i], (ElasticsearchException) actualSuppressed[i]); } } expected = (ElasticsearchException) expected.getCause(); actual = (ElasticsearchException) actual.getCause(); if (expected == null) { assertNull(actual); } } while (expected != null); } public static Tuple<Throwable, ElasticsearchException> randomExceptions() { Throwable actual; ElasticsearchException expected; int type = randomIntBetween(0, 5); switch (type) { case 0: actual = new ClusterBlockException(singleton(DiscoverySettings.NO_MASTER_BLOCK_WRITES)); expected = new ElasticsearchException("Elasticsearch exception [type=cluster_block_exception, " + "reason=blocked by: [SERVICE_UNAVAILABLE/2/no master];]"); break; case 1: actual = new CircuitBreakingException("Data too large", 123, 456); expected = new ElasticsearchException("Elasticsearch exception [type=circuit_breaking_exception, reason=Data too large]"); break; case 2: actual = new SearchParseException(new TestSearchContext(null), "Parse failure", new XContentLocation(12, 98)); expected = new ElasticsearchException("Elasticsearch exception [type=search_parse_exception, reason=Parse failure]"); break; case 3: actual = new IllegalArgumentException("Closed resource", new RuntimeException("Resource")); expected = new ElasticsearchException("Elasticsearch exception [type=illegal_argument_exception, reason=Closed resource]", new ElasticsearchException("Elasticsearch exception [type=runtime_exception, reason=Resource]")); break; case 4: actual = new SearchPhaseExecutionException("search", "all shards failed", new ShardSearchFailure[]{ new ShardSearchFailure(new ParsingException(1, 2, "foobar", null), new SearchShardTarget("node_1", new Index("foo", "_na_"), 1)) }); expected = new ElasticsearchException("Elasticsearch exception [type=search_phase_execution_exception, " + "reason=all shards failed]"); expected.addMetadata("es.phase", "search"); break; case 5: actual = new ElasticsearchException("Parsing failed", new ParsingException(9, 42, "Wrong state", new NullPointerException("Unexpected null value"))); ElasticsearchException expectedCause = new ElasticsearchException("Elasticsearch exception [type=parsing_exception, " + "reason=Wrong state]", new ElasticsearchException("Elasticsearch exception [type=null_pointer_exception, " + "reason=Unexpected null value]")); expected = new ElasticsearchException("Elasticsearch exception [type=exception, reason=Parsing failed]", expectedCause); break; default: throw new UnsupportedOperationException("No randomized exceptions generated for type [" + type + "]"); } if (actual instanceof ElasticsearchException) { ElasticsearchException actualException = (ElasticsearchException) actual; if (randomBoolean()) { int nbHeaders = randomIntBetween(1, 5); Map<String, List<String>> randomHeaders = new HashMap<>(nbHeaders); for (int i = 0; i < nbHeaders; i++) { List<String> values = new ArrayList<>(); int nbValues = randomIntBetween(1, 3); for (int j = 0; j < nbValues; j++) { values.add(frequently() ? randomAlphaOfLength(5) : ""); } randomHeaders.put("header_" + i, values); } for (Map.Entry<String, List<String>> entry : randomHeaders.entrySet()) { actualException.addHeader(entry.getKey(), entry.getValue()); expected.addHeader(entry.getKey(), entry.getValue()); } if (rarely()) { // Empty or null headers are not printed out by the toXContent method actualException.addHeader("ignored", randomBoolean() ? emptyList() : null); } } if (randomBoolean()) { int nbMetadata = randomIntBetween(1, 5); Map<String, List<String>> randomMetadata = new HashMap<>(nbMetadata); for (int i = 0; i < nbMetadata; i++) { List<String> values = new ArrayList<>(); int nbValues = randomIntBetween(1, 3); for (int j = 0; j < nbValues; j++) { values.add(frequently() ? randomAlphaOfLength(5) : ""); } randomMetadata.put("es.metadata_" + i, values); } for (Map.Entry<String, List<String>> entry : randomMetadata.entrySet()) { actualException.addMetadata(entry.getKey(), entry.getValue()); expected.addMetadata(entry.getKey(), entry.getValue()); } if (rarely()) { // Empty or null metadata are not printed out by the toXContent method actualException.addMetadata("es.ignored", randomBoolean() ? emptyList() : null); } } if (randomBoolean()) { int nbResources = randomIntBetween(1, 5); for (int i = 0; i < nbResources; i++) { String resourceType = "type_" + i; String[] resourceIds = new String[randomIntBetween(1, 3)]; for (int j = 0; j < resourceIds.length; j++) { resourceIds[j] = frequently() ? randomAlphaOfLength(5) : ""; } actualException.setResources(resourceType, resourceIds); expected.setResources(resourceType, resourceIds); } } } return new Tuple<>(actual, expected); } }