Add parsing method to BytesRestResponse's error (#22873)

This commit adds a BytesRestResponse.errorFromXContent() method to parse the error returned by BytesRestResponse. It returns a ElasticsearchStatusException instance.
This commit is contained in:
Tanguy Leroux 2017-02-01 10:11:17 +01:00 committed by GitHub
parent 40e1c136de
commit c74679b6b9
4 changed files with 198 additions and 9 deletions

View File

@ -565,7 +565,7 @@ public class ElasticsearchException extends RuntimeException implements ToXConte
* Parses the output of {@link #generateFailureXContent(XContentBuilder, Params, Exception, boolean)} * Parses the output of {@link #generateFailureXContent(XContentBuilder, Params, Exception, boolean)}
*/ */
public static ElasticsearchException failureFromXContent(XContentParser parser) throws IOException { public static ElasticsearchException failureFromXContent(XContentParser parser) throws IOException {
XContentParser.Token token = parser.nextToken(); XContentParser.Token token = parser.currentToken();
ensureFieldName(parser, token, ERROR); ensureFieldName(parser, token, ERROR);
token = parser.nextToken(); token = parser.nextToken();

View File

@ -23,24 +23,29 @@ import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage; import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.logging.log4j.util.Supplier; import org.apache.logging.log4j.util.Supplier;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.logging.ESLoggerFactory; import org.elasticsearch.common.logging.ESLoggerFactory;
import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException; import java.io.IOException;
import static java.util.Collections.singletonMap; import static java.util.Collections.singletonMap;
import static org.elasticsearch.ElasticsearchException.REST_EXCEPTION_SKIP_STACK_TRACE; import static org.elasticsearch.ElasticsearchException.REST_EXCEPTION_SKIP_STACK_TRACE;
import static org.elasticsearch.ElasticsearchException.REST_EXCEPTION_SKIP_STACK_TRACE_DEFAULT; import static org.elasticsearch.ElasticsearchException.REST_EXCEPTION_SKIP_STACK_TRACE_DEFAULT;
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
public class BytesRestResponse extends RestResponse { public class BytesRestResponse extends RestResponse {
public static final String TEXT_CONTENT_TYPE = "text/plain; charset=UTF-8"; public static final String TEXT_CONTENT_TYPE = "text/plain; charset=UTF-8";
private static final String STATUS = "status";
private final RestStatus status; private final RestStatus status;
private final BytesReference content; private final BytesReference content;
private final String contentType; private final String contentType;
@ -136,8 +141,44 @@ public class BytesRestResponse extends RestResponse {
XContentBuilder builder = channel.newErrorBuilder().startObject(); XContentBuilder builder = channel.newErrorBuilder().startObject();
ElasticsearchException.generateFailureXContent(builder, params, e, channel.detailedErrorsEnabled()); ElasticsearchException.generateFailureXContent(builder, params, e, channel.detailedErrorsEnabled());
builder.field("status", status.getStatus()); builder.field(STATUS, status.getStatus());
builder.endObject(); builder.endObject();
return builder; return builder;
} }
public static ElasticsearchStatusException errorFromXContent(XContentParser parser) throws IOException {
XContentParser.Token token = parser.nextToken();
ensureExpectedToken(XContentParser.Token.START_OBJECT, token, parser::getTokenLocation);
ElasticsearchException exception = null;
RestStatus status = null;
String currentFieldName = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
}
if (STATUS.equals(currentFieldName)) {
if (token != XContentParser.Token.FIELD_NAME) {
ensureExpectedToken(XContentParser.Token.VALUE_NUMBER, token, parser::getTokenLocation);
status = RestStatus.fromCode(parser.intValue());
}
} else {
exception = ElasticsearchException.failureFromXContent(parser);
}
}
if (exception == null) {
throw new IllegalStateException("Failed to parse elasticsearch status exception: no exception was found");
}
ElasticsearchStatusException result = new ElasticsearchStatusException(exception.getMessage(), status, exception.getCause());
for (String header : exception.getHeaderKeys()) {
result.addHeader(header, exception.getHeader(header));
}
for (String metadata : exception.getMetadataKeys()) {
result.addMetadata(metadata, exception.getMetadata(metadata));
}
return result;
}
} }

View File

@ -604,6 +604,7 @@ public class ElasticsearchExceptionTests extends ESTestCase {
ElasticsearchException parsedFailure; ElasticsearchException parsedFailure;
try (XContentParser parser = createParser(xContent, failureBytes)) { try (XContentParser parser = createParser(xContent, failureBytes)) {
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken());
parsedFailure = ElasticsearchException.failureFromXContent(parser); parsedFailure = ElasticsearchException.failureFromXContent(parser);
assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken()); assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken());
assertNull(parser.nextToken()); assertNull(parser.nextToken());
@ -629,6 +630,7 @@ public class ElasticsearchExceptionTests extends ESTestCase {
ElasticsearchException parsedFailure; ElasticsearchException parsedFailure;
try (XContentParser parser = createParser(xContent, failureBytes)) { try (XContentParser parser = createParser(xContent, failureBytes)) {
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken());
parsedFailure = ElasticsearchException.failureFromXContent(parser); parsedFailure = ElasticsearchException.failureFromXContent(parser);
assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken()); assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken());
assertNull(parser.nextToken()); assertNull(parser.nextToken());
@ -670,14 +672,14 @@ public class ElasticsearchExceptionTests extends ESTestCase {
}, expectedJson); }, expectedJson);
} }
private static void assertDeepEquals(ElasticsearchException expected, ElasticsearchException actual) { public static void assertDeepEquals(ElasticsearchException expected, ElasticsearchException actual) {
if (expected == null) {
assertNull(actual);
} else {
assertNotNull(actual);
}
do { do {
if (expected == null) {
assertNull(actual);
} else {
assertNotNull(actual);
}
assertEquals(expected.getMessage(), actual.getMessage()); assertEquals(expected.getMessage(), actual.getMessage());
assertEquals(expected.getHeaders(), actual.getHeaders()); assertEquals(expected.getHeaders(), actual.getHeaders());
assertEquals(expected.getMetadata(), actual.getMetadata()); assertEquals(expected.getMetadata(), actual.getMetadata());

View File

@ -20,12 +20,19 @@
package org.elasticsearch.rest; package org.elasticsearch.rest;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.ResourceAlreadyExistsException;
import org.elasticsearch.ResourceNotFoundException;
import org.elasticsearch.action.search.SearchPhaseExecutionException; import org.elasticsearch.action.search.SearchPhaseExecutionException;
import org.elasticsearch.action.search.ShardSearchFailure; import org.elasticsearch.action.search.ShardSearchFailure;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.common.xcontent.NamedXContentRegistry; 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.index.Index;
import org.elasticsearch.search.SearchShardTarget; import org.elasticsearch.search.SearchShardTarget;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
@ -37,6 +44,7 @@ import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import static org.elasticsearch.ElasticsearchExceptionTests.assertDeepEquals;
import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
@ -210,6 +218,144 @@ public class BytesRestResponseTests extends ESTestCase {
assertThat(content, containsString("\"status\":" + 500)); 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 = randomAsciiOfLength(5);
String resourceId = randomAsciiOfLength(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 {
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> {
try (XContentBuilder builder = XContentBuilder.builder(randomFrom(XContentType.values()).xContent())) {
builder.startObject();
builder.field("status", randomFrom(RestStatus.values()));
builder.endObject();
try (XContentParser parser = createParser(builder.contentType().xContent(), builder.bytes())) {
BytesRestResponse.errorFromXContent(parser);
}
}
});
assertEquals("Unable to parse elasticsearch status exception", e.getMessage());
}
public static class WithHeadersException extends ElasticsearchException { public static class WithHeadersException extends ElasticsearchException {
WithHeadersException() { WithHeadersException() {