From fc6113b0268b98d02392e146d765e3279f27eff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=BCscher?= Date: Fri, 20 Jan 2017 12:32:55 +0100 Subject: [PATCH] Add parsing from xContent to ShardSearchFailure In preparation for being able to parse SearchResponse from its rest representation, this adds fromXContent to ShardSearchFailure. --- .../action/search/ShardSearchFailure.java | 58 +++++++++++- .../search/ShardSearchFailureTests.java | 91 +++++++++++++++++++ 2 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 core/src/test/java/org/elasticsearch/action/search/ShardSearchFailureTests.java diff --git a/core/src/main/java/org/elasticsearch/action/search/ShardSearchFailure.java b/core/src/main/java/org/elasticsearch/action/search/ShardSearchFailure.java index 74bd9d4b7a40c..2aa0ad3c7be57 100644 --- a/core/src/main/java/org/elasticsearch/action/search/ShardSearchFailure.java +++ b/core/src/main/java/org/elasticsearch/action/search/ShardSearchFailure.java @@ -22,21 +22,34 @@ import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.action.ShardOperationFailedException; +import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.index.Index; +import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.search.SearchException; import org.elasticsearch.search.SearchShardTarget; import java.io.IOException; +import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; +import static org.elasticsearch.common.xcontent.XContentParserUtils.throwUnknownField; +import static org.elasticsearch.common.xcontent.XContentParserUtils.throwUnknownToken; + /** * Represents a failure to search on a specific shard. */ public class ShardSearchFailure implements ShardOperationFailedException { + private static final String REASON_FIELD = "reason"; + private static final String NODE_FIELD = "node"; + private static final String INDEX_FIELD = "index"; + private static final String SHARD_FIELD = "shard"; + public static final ShardSearchFailure[] EMPTY_ARRAY = new ShardSearchFailure[0]; private SearchShardTarget shardTarget; @@ -68,7 +81,7 @@ public ShardSearchFailure(String reason, SearchShardTarget shardTarget) { this(reason, shardTarget, RestStatus.INTERNAL_SERVER_ERROR); } - public ShardSearchFailure(String reason, SearchShardTarget shardTarget, RestStatus status) { + private ShardSearchFailure(String reason, SearchShardTarget shardTarget, RestStatus status) { this.shardTarget = shardTarget; this.reason = reason; this.status = status; @@ -153,13 +166,13 @@ public void writeTo(StreamOutput out) throws IOException { @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.field("shard", shardId()); - builder.field("index", index()); + builder.field(SHARD_FIELD, shardId()); + builder.field(INDEX_FIELD, index()); if (shardTarget != null) { - builder.field("node", shardTarget.getNodeId()); + builder.field(NODE_FIELD, shardTarget.getNodeId()); } if (cause != null) { - builder.field("reason"); + builder.field(REASON_FIELD); builder.startObject(); ElasticsearchException.generateThrowableXContent(builder, params, cause); builder.endObject(); @@ -167,6 +180,41 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws return builder; } + public static ShardSearchFailure fromXContent(XContentParser parser) throws IOException { + XContentParser.Token token; + ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation); + String currentFieldName = null; + int shardId = -1; + String indexName = null; + String nodeId = null; + ElasticsearchException exception = null; + while((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else if (token.isValue()) { + if (SHARD_FIELD.equals(currentFieldName)) { + shardId = parser.intValue(); + } else if (INDEX_FIELD.equals(currentFieldName)) { + indexName = parser.text(); + } else if (NODE_FIELD.equals(currentFieldName)) { + nodeId = parser.text(); + } else { + throwUnknownField(currentFieldName, parser.getTokenLocation()); + } + } else if (token == XContentParser.Token.START_OBJECT) { + if (REASON_FIELD.equals(currentFieldName)) { + exception = ElasticsearchException.fromXContent(parser); + } else { + throwUnknownField(currentFieldName, parser.getTokenLocation()); + } + } else { + throwUnknownToken(token, parser.getTokenLocation()); + } + } + return new ShardSearchFailure(exception, + new SearchShardTarget(nodeId, new ShardId(new Index(indexName, IndexMetaData.INDEX_UUID_NA_VALUE), shardId))); + } + @Override public Throwable getCause() { return cause; diff --git a/core/src/test/java/org/elasticsearch/action/search/ShardSearchFailureTests.java b/core/src/test/java/org/elasticsearch/action/search/ShardSearchFailureTests.java new file mode 100644 index 0000000000000..d73046934d7e1 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/action/search/ShardSearchFailureTests.java @@ -0,0 +1,91 @@ +/* + * 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.search; + +import org.elasticsearch.common.ParsingException; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.index.Index; +import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.search.SearchShardTarget; +import org.elasticsearch.test.ESTestCase; + +import java.io.IOException; + +import static org.elasticsearch.common.xcontent.XContentHelper.toXContent; + +public class ShardSearchFailureTests extends ESTestCase { + + public static ShardSearchFailure createTestItem() { + String randomMessage = randomAsciiOfLengthBetween(3, 20); + Exception ex = new ParsingException(0, 0, randomMessage , new IllegalArgumentException("some bad argument")); + String nodeId = randomAsciiOfLengthBetween(5, 10); + String indexName = randomAsciiOfLengthBetween(5, 10); + String indexUuid = randomAsciiOfLengthBetween(5, 10); + int shardId = randomInt(); + return new ShardSearchFailure(ex, + new SearchShardTarget(nodeId, new ShardId(new Index(indexName, indexUuid), shardId))); + } + + public void testFromXContent() throws IOException { + ShardSearchFailure response = createTestItem(); + XContentType xContentType = randomFrom(XContentType.values()); + boolean humanReadable = randomBoolean(); + BytesReference originalBytes = toXContent(response, xContentType, humanReadable); + + ShardSearchFailure parsed; + try (XContentParser parser = createParser(xContentType.xContent(), originalBytes)) { + assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); + parsed = ShardSearchFailure.fromXContent(parser); + assertEquals(XContentParser.Token.END_OBJECT, parser.currentToken()); + assertNull(parser.nextToken()); + } + assertEquals(response.index(), parsed.index()); + assertEquals(response.shard().getNodeId(), parsed.shard().getNodeId()); + assertEquals(response.shardId(), parsed.shardId()); + + // we cannot compare the cause, because it will be wrapped in an outer ElasticSearchException + // best effort: try to check that the original message appears somewhere in the rendered xContent + String originalMsg = response.getCause().getMessage(); + assertEquals(parsed.getCause().getMessage(), "Elasticsearch exception [type=parsing_exception, reason=" + originalMsg + "]"); + String nestedMsg = response.getCause().getCause().getMessage(); + assertEquals(parsed.getCause().getCause().getMessage(), + "Elasticsearch exception [type=illegal_argument_exception, reason=" + nestedMsg + "]"); + } + + public void testToXContent() throws IOException { + ShardSearchFailure failure = new ShardSearchFailure(new ParsingException(0, 0, "some message", null), + new SearchShardTarget("nodeId", new ShardId(new Index("indexName", "indexUuid"), 123))); + BytesReference xContent = toXContent(failure, XContentType.JSON); + assertEquals( + "{\"shard\":123," + + "\"index\":\"indexName\"," + + "\"node\":\"nodeId\"," + + "\"reason\":{" + + "\"type\":\"parsing_exception\"," + + "\"reason\":\"some message\"," + + "\"line\":0," + + "\"col\":0" + + "}" + + "}", + xContent.utf8ToString()); + } +}