From 07710eb71018191accab6f18120c1bf1ad2e9f9d Mon Sep 17 00:00:00 2001 From: Tanguy Leroux Date: Tue, 10 Jan 2017 09:36:25 +0100 Subject: [PATCH 1/2] Add fromXContent() methods to IndexResponse This commit adds the parsing fromXContent() methods to the IndexResponse class. The method is based on a ObjectParser because it is easier to use when parsing parent abstract classes like DocWriteResponse. It also changes the ReplicationResponse.ShardInfo so that it now implements ToXContentObject. This way, the ShardInfo.fromXContent() method can be used by the IndexResponse's ObjectParser. --- .../action/DocWriteResponse.java | 45 ++++- .../action/index/IndexResponse.java | 39 +++- .../replication/ReplicationResponse.java | 14 +- .../action/index/IndexRequestTests.java | 2 +- .../action/index/IndexResponseTests.java | 169 ++++++++++++++++++ .../replication/ReplicationResponseTests.java | 65 ++----- .../org/elasticsearch/test/RandomObjects.java | 58 +++++- 7 files changed, 316 insertions(+), 76 deletions(-) create mode 100644 core/src/test/java/org/elasticsearch/action/index/IndexResponseTests.java diff --git a/core/src/main/java/org/elasticsearch/action/DocWriteResponse.java b/core/src/main/java/org/elasticsearch/action/DocWriteResponse.java index ee55fd9c2ad51..2ea22803356b2 100644 --- a/core/src/main/java/org/elasticsearch/action/DocWriteResponse.java +++ b/core/src/main/java/org/elasticsearch/action/DocWriteResponse.java @@ -24,9 +24,12 @@ import org.elasticsearch.action.support.WriteResponse; import org.elasticsearch.action.support.replication.ReplicationResponse; import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.ParseField; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ObjectParser; import org.elasticsearch.common.xcontent.StatusToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.index.IndexSettings; @@ -39,11 +42,23 @@ import java.net.URISyntaxException; import java.util.Locale; +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; + /** * A base class for the response of a write operation that involves a single doc */ public abstract class DocWriteResponse extends ReplicationResponse implements WriteResponse, StatusToXContentObject { + private static final String _SHARDS = "_shards"; + private static final String _INDEX = "_index"; + private static final String _TYPE = "_type"; + private static final String _ID = "_id"; + private static final String _VERSION = "_version"; + private static final String _SEQ_NO = "_seq_no"; + private static final String RESULT = "result"; + private static final String FORCED_REFRESH = "forced_refresh"; + /** * An enum that represents the the results of CRUD operations, primarily used to communicate the type of * operation that occurred. @@ -253,18 +268,32 @@ public final XContentBuilder toXContent(XContentBuilder builder, Params params) public XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException { ReplicationResponse.ShardInfo shardInfo = getShardInfo(); - builder.field("_index", shardId.getIndexName()) - .field("_type", type) - .field("_id", id) - .field("_version", version) - .field("result", getResult().getLowercase()); + builder.field(_INDEX, shardId.getIndexName()) + .field(_TYPE, type) + .field(_ID, id) + .field(_VERSION, version) + .field(RESULT, getResult().getLowercase()); if (forcedRefresh) { - builder.field("forced_refresh", true); + builder.field(FORCED_REFRESH, true); } - shardInfo.toXContent(builder, params); + builder.field(_SHARDS, shardInfo); if (getSeqNo() >= 0) { - builder.field("_seq_no", getSeqNo()); + builder.field(_SEQ_NO, getSeqNo()); } return builder; } + + /** + * Declare the {@link ObjectParser} fields to use when parsing a {@link DocWriteResponse} + */ + protected static void declareParserFields(ConstructingObjectParser objParser) { + objParser.declareString(constructorArg(), new ParseField(_INDEX)); + objParser.declareString(constructorArg(), new ParseField(_TYPE)); + objParser.declareString(constructorArg(), new ParseField(_ID)); + objParser.declareLong(constructorArg(), new ParseField(_VERSION)); + objParser.declareString(constructorArg(), new ParseField(RESULT)); + objParser.declareLong(optionalConstructorArg(), new ParseField(_SEQ_NO)); + objParser.declareBoolean(DocWriteResponse::setForcedRefresh, new ParseField(FORCED_REFRESH)); + objParser.declareObject(DocWriteResponse::setShardInfo, (p, c) -> ShardInfo.fromXContent(p), new ParseField(_SHARDS)); + } } diff --git a/core/src/main/java/org/elasticsearch/action/index/IndexResponse.java b/core/src/main/java/org/elasticsearch/action/index/IndexResponse.java index c508546b4339c..219e81b9622c9 100644 --- a/core/src/main/java/org/elasticsearch/action/index/IndexResponse.java +++ b/core/src/main/java/org/elasticsearch/action/index/IndexResponse.java @@ -20,13 +20,21 @@ package org.elasticsearch.action.index; import org.elasticsearch.action.DocWriteResponse; +import org.elasticsearch.cluster.metadata.IndexMetaData; +import org.elasticsearch.common.ParseField; import org.elasticsearch.common.Strings; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.index.Index; +import org.elasticsearch.index.seqno.SequenceNumbersService; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.rest.RestStatus; import java.io.IOException; +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; + /** * A response of an index operation, * @@ -35,6 +43,8 @@ */ public class IndexResponse extends DocWriteResponse { + private static final String CREATED = "created"; + public IndexResponse() { } @@ -64,7 +74,34 @@ public String toString() { @Override public XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException { super.innerToXContent(builder, params); - builder.field("created", result == Result.CREATED); + builder.field(CREATED, result == Result.CREATED); return builder; } + + /** + * ConstructingObjectParser used to parse the {@link IndexResponse}. We use a ObjectParser here + * because most fields are parsed by the parent abstract class {@link DocWriteResponse} and it's + * not easy to parse part of the fields in the parent class and other fields in the children class + * using the usual streamed parsing method. + */ + private static final ConstructingObjectParser PARSER; + static { + PARSER = new ConstructingObjectParser<>(IndexResponse.class.getName(), + args -> { + // index uuid and shard id are unknown and can't be parsed back for now. + ShardId shardId = new ShardId(new Index((String) args[0], IndexMetaData.INDEX_UUID_NA_VALUE), -1); + String type = (String) args[1]; + String id = (String) args[2]; + long version = (long) args[3]; + long seqNo = (args[5] != null) ? (long) args[5] : SequenceNumbersService.UNASSIGNED_SEQ_NO; + boolean created = (boolean) args[6]; + return new IndexResponse(shardId, type, id, seqNo, version, created); + }); + DocWriteResponse.declareParserFields(PARSER); + PARSER.declareBoolean(constructorArg(), new ParseField(CREATED)); + } + + public static IndexResponse fromXContent(XContentParser parser) throws IOException { + return PARSER.apply(parser, null); + } } diff --git a/core/src/main/java/org/elasticsearch/action/support/replication/ReplicationResponse.java b/core/src/main/java/org/elasticsearch/action/support/replication/ReplicationResponse.java index 2f701c8728631..91c3089de20e0 100644 --- a/core/src/main/java/org/elasticsearch/action/support/replication/ReplicationResponse.java +++ b/core/src/main/java/org/elasticsearch/action/support/replication/ReplicationResponse.java @@ -73,7 +73,7 @@ public void setShardInfo(ShardInfo shardInfo) { this.shardInfo = shardInfo; } - public static class ShardInfo implements Streamable, ToXContent { + public static class ShardInfo implements Streamable, ToXContentObject { private static final String _SHARDS = "_shards"; private static final String TOTAL = "total"; @@ -179,7 +179,7 @@ public void writeTo(StreamOutput out) throws IOException { @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(_SHARDS); + builder.startObject(); builder.field(TOTAL, total); builder.field(SUCCESSFUL, successful); builder.field(FAILED, getFailed()); @@ -195,18 +195,12 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws } public static ShardInfo fromXContent(XContentParser parser) throws IOException { - XContentParser.Token token = parser.nextToken(); - ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation); - - String currentFieldName = parser.currentName(); - if (_SHARDS.equals(currentFieldName) == false) { - throwUnknownField(currentFieldName, parser.getTokenLocation()); - } - token = parser.nextToken(); + XContentParser.Token token = parser.currentToken(); ensureExpectedToken(XContentParser.Token.START_OBJECT, token, parser::getTokenLocation); int total = 0, successful = 0; List failuresList = null; + String currentFieldName = null; while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { if (token == XContentParser.Token.FIELD_NAME) { currentFieldName = parser.currentName(); diff --git a/core/src/test/java/org/elasticsearch/action/index/IndexRequestTests.java b/core/src/test/java/org/elasticsearch/action/index/IndexRequestTests.java index 1d1532c49196f..a722529626843 100644 --- a/core/src/test/java/org/elasticsearch/action/index/IndexRequestTests.java +++ b/core/src/test/java/org/elasticsearch/action/index/IndexRequestTests.java @@ -147,7 +147,7 @@ public void testIndexResponse() { assertEquals("IndexResponse[index=" + shardId.getIndexName() + ",type=" + type + ",id="+ id + ",version=" + version + ",result=" + (created ? "created" : "updated") + ",seqNo=" + SequenceNumbersService.UNASSIGNED_SEQ_NO + - ",shards={\"_shards\":{\"total\":" + total + ",\"successful\":" + successful + ",\"failed\":0}}]", + ",shards={\"total\":" + total + ",\"successful\":" + successful + ",\"failed\":0}]", indexResponse.toString()); } } diff --git a/core/src/test/java/org/elasticsearch/action/index/IndexResponseTests.java b/core/src/test/java/org/elasticsearch/action/index/IndexResponseTests.java new file mode 100644 index 0000000000000..736ebe16ba372 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/action/index/IndexResponseTests.java @@ -0,0 +1,169 @@ +/* + * 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.index; + +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.action.support.replication.ReplicationResponse; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.util.CollectionUtils; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.RandomObjects; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import static org.elasticsearch.common.xcontent.XContentHelper.toXContent; + +public class IndexResponseTests extends ESTestCase { + + public void testToXContent() throws IOException { + { + IndexResponse indexResponse = new IndexResponse(new ShardId("index", "index_uuid", 0), "type", "id", 3, 5, true); + String output = Strings.toString(indexResponse); + assertEquals("{\"_index\":\"index\",\"_type\":\"type\",\"_id\":\"id\",\"_version\":5,\"result\":\"created\",\"_shards\":null," + + "\"_seq_no\":3,\"created\":true}", output); + } + { + IndexResponse indexResponse = new IndexResponse(new ShardId("index", "index_uuid", 0), "type", "id", -1, 7, true); + indexResponse.setForcedRefresh(true); + indexResponse.setShardInfo(new ReplicationResponse.ShardInfo(10, 5)); + String output = Strings.toString(indexResponse); + assertEquals("{\"_index\":\"index\",\"_type\":\"type\",\"_id\":\"id\",\"_version\":7,\"result\":\"created\"," + + "\"forced_refresh\":true,\"_shards\":{\"total\":10,\"successful\":5,\"failed\":0},\"created\":true}", output); + } + } + + public void testToAndFromXContent() throws IOException { + final XContentType xContentType = randomFrom(XContentType.values()); + + // Create a random IndexResponse and converts it to XContent in bytes + IndexResponse indexResponse = randomIndexResponse(); + BytesReference indexResponseBytes = toXContent(indexResponse, xContentType); + + // Parse the XContent bytes to obtain a parsed + IndexResponse parsedIndexResponse; + try (XContentParser parser = createParser(xContentType.xContent(), indexResponseBytes)) { + parsedIndexResponse = IndexResponse.fromXContent(parser); + assertNull(parser.nextToken()); + } + + // We can't use equals() to compare the original and the parsed index response + // because the random index response can contain shard failures with exceptions, + // and those exceptions are not parsed back with the same types. + + // Print the parsed object out and test that the output is the same as the original output + BytesReference parsedIndexResponseBytes = toXContent(parsedIndexResponse, xContentType); + try (XContentParser parser = createParser(xContentType.xContent(), parsedIndexResponseBytes)) { + assertIndexResponse(indexResponse, parser.map()); + } + } + + private static void assertIndexResponse(IndexResponse expected, Map actual) { + assertEquals(expected.getIndex(), actual.get("_index")); + assertEquals(expected.getType(), actual.get("_type")); + assertEquals(expected.getId(), actual.get("_id")); + assertEquals(expected.getVersion(), ((Integer) actual.get("_version")).longValue()); + assertEquals(expected.getResult().getLowercase(), actual.get("result")); + if (expected.forcedRefresh()) { + assertTrue((Boolean) actual.get("forced_refresh")); + } else { + assertFalse(actual.containsKey("forced_refresh")); + } + if (expected.getSeqNo() >= 0) { + assertEquals(expected.getSeqNo(), ((Integer) actual.get("_seq_no")).longValue()); + } else { + assertFalse(actual.containsKey("_seq_no")); + } + + Map actualShards = (Map) actual.get("_shards"); + assertNotNull(actualShards); + assertEquals(expected.getShardInfo().getTotal(), actualShards.get("total")); + assertEquals(expected.getShardInfo().getSuccessful(), actualShards.get("successful")); + assertEquals(expected.getShardInfo().getFailed(), actualShards.get("failed")); + + List> actualFailures = (List>) actualShards.get("failures"); + if (CollectionUtils.isEmpty(expected.getShardInfo().getFailures())) { + assertNull(actualFailures); + } else { + assertEquals(expected.getShardInfo().getFailures().length, actualFailures.size()); + for (int i = 0; i < expected.getShardInfo().getFailures().length; i++) { + ReplicationResponse.ShardInfo.Failure failure = expected.getShardInfo().getFailures()[i]; + Map actualFailure = actualFailures.get(i); + + assertEquals(failure.index(), actualFailure.get("_index")); + assertEquals(failure.shardId(), actualFailure.get("_shard")); + assertEquals(failure.nodeId(), actualFailure.get("_node")); + assertEquals(failure.status(), RestStatus.valueOf((String) actualFailure.get("status"))); + assertEquals(failure.primary(), actualFailure.get("primary")); + + Throwable cause = failure.getCause(); + Map actualClause = (Map) actualFailure.get("reason"); + assertNotNull(actualClause); + while (cause != null) { + // The expected IndexResponse has been converted in XContent, then the resulting bytes have been + // parsed to create a new parsed IndexResponse. During this process, the type of the exceptions + // have been lost. + assertEquals("exception", actualClause.get("type")); + String expectedMessage = "Elasticsearch exception [type=" + ElasticsearchException.getExceptionName(cause) + + ", reason=" + cause.getMessage() + "]"; + assertEquals(expectedMessage, actualClause.get("reason")); + + if (cause instanceof ElasticsearchException) { + ElasticsearchException ex = (ElasticsearchException) cause; + Map actualHeaders = (Map) actualClause.get("header"); + + // When a IndexResponse is converted to XContent, the exception headers that start with "es." + // are added to the XContent as fields with the prefix removed. Other headers are added under + // a "header" root object. + // In the test, the "es." prefix is lost when the XContent is generating, so when the parsed + // IndexResponse is converted back to XContent all exception headers are under the "header" object. + for (String name : ex.getHeaderKeys()) { + assertEquals(ex.getHeader(name).get(0), actualHeaders.get(name.replaceFirst("es.", ""))); + } + } + actualClause = (Map) actualClause.get("caused_by"); + cause = cause.getCause(); + } + } + } + } + + private static IndexResponse randomIndexResponse() { + ShardId shardId = new ShardId(randomAsciiOfLength(5), randomAsciiOfLength(5), randomIntBetween(0, 5)); + String type = randomAsciiOfLength(5); + String id = randomAsciiOfLength(5); + long seqNo = randomIntBetween(-2, 5); + long version = (long) randomIntBetween(0, 5); + boolean created = randomBoolean(); + + IndexResponse indexResponse = new IndexResponse(shardId, type, id, seqNo, version, created); + //indexResponse.setForcedRefresh(randomBoolean()); + indexResponse.setForcedRefresh(true); + indexResponse.setShardInfo(RandomObjects.randomShardInfo(random(), randomBoolean())); + return indexResponse; + } + +} diff --git a/core/src/test/java/org/elasticsearch/action/support/replication/ReplicationResponseTests.java b/core/src/test/java/org/elasticsearch/action/support/replication/ReplicationResponseTests.java index 658853f9598c1..0972a91c8ec1e 100644 --- a/core/src/test/java/org/elasticsearch/action/support/replication/ReplicationResponseTests.java +++ b/core/src/test/java/org/elasticsearch/action/support/replication/ReplicationResponseTests.java @@ -20,17 +20,17 @@ package org.elasticsearch.action.support.replication; import org.elasticsearch.ElasticsearchException; -import org.elasticsearch.action.RoutingMissingException; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.Index; -import org.elasticsearch.index.shard.IndexShardRecoveringException; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.EqualsHashCodeTestUtils; +import org.elasticsearch.test.RandomObjects; import java.io.IOException; import java.util.ArrayList; @@ -42,6 +42,7 @@ import java.util.function.Supplier; import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode; +import static org.hamcrest.Matchers.instanceOf; public class ReplicationResponseTests extends ESTestCase { @@ -64,12 +65,13 @@ public void testShardInfoEqualsAndHashcode() { new ReplicationResponse.ShardInfo(shardInfo.getTotal(), shardInfo.getSuccessful() + 1, shardInfo.getFailures())); mutations.add(() -> { int nbFailures = randomIntBetween(1, 5); - return new ReplicationResponse.ShardInfo(shardInfo.getTotal(), shardInfo.getSuccessful(), randomFailures(nbFailures)); + ReplicationResponse.ShardInfo.Failure[] randomFailures = RandomObjects.randomShardInfoFailures(random(), nbFailures); + return new ReplicationResponse.ShardInfo(shardInfo.getTotal(), shardInfo.getSuccessful(), randomFailures); }); return randomFrom(mutations).get(); }; - checkEqualsAndHashCode(randomShardInfo(), copy, mutate); + checkEqualsAndHashCode(RandomObjects.randomShardInfo(random(), randomBoolean()), copy, mutate); } public void testFailureEqualsAndHashcode() { @@ -127,7 +129,7 @@ public void testFailureEqualsAndHashcode() { return randomFrom(mutations).get(); }; - checkEqualsAndHashCode(randomFailure(), copy, mutate); + checkEqualsAndHashCode(RandomObjects.randomShardInfoFailure(random()), copy, mutate); } public void testShardInfoToXContent() throws IOException { @@ -136,11 +138,9 @@ public void testShardInfoToXContent() throws IOException { final ReplicationResponse.ShardInfo shardInfo = new ReplicationResponse.ShardInfo(5, 3); final BytesReference shardInfoBytes = XContentHelper.toXContent(shardInfo, xContentType); - // Expected JSON is {"_shards":{"total":5,"successful":3,"failed":0}} + // Expected JSON is {"total":5,"successful":3,"failed":0} + assertThat(shardInfo, instanceOf(ToXContentObject.class)); try (XContentParser parser = createParser(xContentType.xContent(), shardInfoBytes)) { - assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); - assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken()); - assertEquals("_shards", parser.currentName()); assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken()); assertEquals("total", parser.currentName()); @@ -155,7 +155,6 @@ public void testShardInfoToXContent() throws IOException { assertEquals(XContentParser.Token.VALUE_NUMBER, parser.nextToken()); assertEquals(shardInfo.getFailed(), parser.intValue()); assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken()); - assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken()); assertNull(parser.nextToken()); } } @@ -168,10 +167,9 @@ public void testShardInfoToAndFromXContent() throws IOException { ReplicationResponse.ShardInfo parsedShardInfo; try (XContentParser parser = createParser(xContentType.xContent(), shardInfoBytes)) { - // Move to the start object that was manually added when building the object + // Move to the first start object assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); parsedShardInfo = ReplicationResponse.ShardInfo.fromXContent(parser); - assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken()); assertNull(parser.nextToken()); } // We can use assertEquals because the shardInfo doesn't have a failure (and exceptions) @@ -184,13 +182,10 @@ public void testShardInfoToAndFromXContent() throws IOException { public void testShardInfoWithFailureToXContent() throws IOException { final XContentType xContentType = randomFrom(XContentType.values()); - final ReplicationResponse.ShardInfo shardInfo = randomShardInfo(); + final ReplicationResponse.ShardInfo shardInfo = RandomObjects.randomShardInfo(random(), true); final BytesReference shardInfoBytes = XContentHelper.toXContent(shardInfo, xContentType); try (XContentParser parser = createParser(xContentType.xContent(), shardInfoBytes)) { - assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); - assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken()); - assertEquals("_shards", parser.currentName()); assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken()); assertEquals("total", parser.currentName()); @@ -216,7 +211,6 @@ public void testShardInfoWithFailureToXContent() throws IOException { assertEquals(XContentParser.Token.END_ARRAY, parser.nextToken()); } - assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken()); assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken()); assertNull(parser.nextToken()); } @@ -225,15 +219,14 @@ public void testShardInfoWithFailureToXContent() throws IOException { public void testRandomShardInfoFromXContent() throws IOException { final XContentType xContentType = randomFrom(XContentType.values()); - final ReplicationResponse.ShardInfo shardInfo = randomShardInfo(); + final ReplicationResponse.ShardInfo shardInfo = RandomObjects.randomShardInfo(random(), randomBoolean()); final BytesReference shardInfoBytes = XContentHelper.toXContent(shardInfo, xContentType); ReplicationResponse.ShardInfo parsedShardInfo; try (XContentParser parser = createParser(xContentType.xContent(), shardInfoBytes)) { - // Move to the start object that was manually added when building the object + // Move to the first start object assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); parsedShardInfo = ReplicationResponse.ShardInfo.fromXContent(parser); - assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken()); assertNull(parser.nextToken()); } @@ -266,7 +259,7 @@ public void testRandomShardInfoFromXContent() throws IOException { public void testRandomFailureToXContent() throws IOException { final XContentType xContentType = randomFrom(XContentType.values()); - final ReplicationResponse.ShardInfo.Failure shardInfoFailure = randomFailure(); + final ReplicationResponse.ShardInfo.Failure shardInfoFailure = RandomObjects.randomShardInfoFailure(random()); final BytesReference shardInfoBytes = XContentHelper.toXContent(shardInfoFailure, xContentType); try (XContentParser parser = createParser(xContentType.xContent(), shardInfoBytes)) { @@ -277,7 +270,7 @@ public void testRandomFailureToXContent() throws IOException { public void testRandomFailureToAndFromXContent() throws IOException { final XContentType xContentType = randomFrom(XContentType.values()); - final ReplicationResponse.ShardInfo.Failure shardInfoFailure = randomFailure(); + final ReplicationResponse.ShardInfo.Failure shardInfoFailure = RandomObjects.randomShardInfoFailure(random());; final BytesReference shardInfoBytes = XContentHelper.toXContent(shardInfoFailure, xContentType); ReplicationResponse.ShardInfo.Failure parsedFailure; @@ -358,32 +351,4 @@ private static void assertThrowable(XContentParser parser, Throwable cause) thro } assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken()); } - - private static ReplicationResponse.ShardInfo randomShardInfo() { - int total = randomIntBetween(1, 10); - int successful = randomIntBetween(0, total); - return new ReplicationResponse.ShardInfo(total, successful, randomFailures(Math.max(0, (total - successful)))); - } - - private static ReplicationResponse.ShardInfo.Failure[] randomFailures(int nbFailures) { - List randomFailures = new ArrayList<>(nbFailures); - for (int i = 0; i < nbFailures; i++) { - randomFailures.add(randomFailure()); - } - return randomFailures.toArray(new ReplicationResponse.ShardInfo.Failure[nbFailures]); - } - - private static ReplicationResponse.ShardInfo.Failure randomFailure() { - return new ReplicationResponse.ShardInfo.Failure( - new ShardId(randomAsciiOfLength(5), randomAsciiOfLength(5), randomIntBetween(0, 5)), - randomAsciiOfLength(3), - randomFrom( - new IndexShardRecoveringException(new ShardId("_test", "_0", 5)), - new ElasticsearchException(new IllegalArgumentException("argument is wrong")), - new RoutingMissingException("_test", "_type", "_id") - ), - randomFrom(RestStatus.values()), - randomBoolean() - ); - } } diff --git a/test/framework/src/main/java/org/elasticsearch/test/RandomObjects.java b/test/framework/src/main/java/org/elasticsearch/test/RandomObjects.java index f23e243074d69..7611c17492e05 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/RandomObjects.java +++ b/test/framework/src/main/java/org/elasticsearch/test/RandomObjects.java @@ -19,9 +19,11 @@ package org.elasticsearch.test; -import com.carrotsearch.randomizedtesting.generators.RandomNumbers; import com.carrotsearch.randomizedtesting.generators.RandomPicks; import com.carrotsearch.randomizedtesting.generators.RandomStrings; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.action.RoutingMissingException; +import org.elasticsearch.action.support.replication.ReplicationResponse.ShardInfo; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.collect.Tuple; @@ -29,15 +31,22 @@ import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.index.shard.IndexShardRecoveringException; +import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.rest.RestStatus; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.Base64; import java.util.List; import java.util.Random; +import static com.carrotsearch.randomizedtesting.generators.RandomNumbers.randomIntBetween; +import static com.carrotsearch.randomizedtesting.generators.RandomStrings.randomAsciiOfLength; import static com.carrotsearch.randomizedtesting.generators.RandomStrings.randomUnicodeOfLengthBetween; +import static org.elasticsearch.test.ESTestCase.randomFrom; public final class RandomObjects { @@ -56,10 +65,10 @@ private RandomObjects() { * @param xContentType the content type, used to determine what the expected values are for float numbers. */ public static Tuple, List> randomStoredFieldValues(Random random, XContentType xContentType) { - int numValues = RandomNumbers.randomIntBetween(random, 1, 5); + int numValues = randomIntBetween(random, 1, 5); List originalValues = new ArrayList<>(); List expectedParsedValues = new ArrayList<>(); - int dataType = RandomNumbers.randomIntBetween(random, 0, 8); + int dataType = randomIntBetween(random, 0, 8); for (int i = 0; i < numValues; i++) { switch(dataType) { case 0: @@ -153,7 +162,7 @@ public static BytesReference randomSource(Random random) { * Randomly adds fields, objects, or arrays to the provided builder. The maximum depth is 5. */ private static void addFields(Random random, XContentBuilder builder, int currentDepth) throws IOException { - int numFields = RandomNumbers.randomIntBetween(random, 1, 5); + int numFields = randomIntBetween(random, 1, 5); for (int i = 0; i < numFields; i++) { if (currentDepth < 5 && random.nextBoolean()) { if (random.nextBoolean()) { @@ -162,7 +171,7 @@ private static void addFields(Random random, XContentBuilder builder, int curren builder.endObject(); } else { builder.startArray(RandomStrings.randomAsciiOfLengthBetween(random, 3, 10)); - int numElements = RandomNumbers.randomIntBetween(random, 1, 5); + int numElements = randomIntBetween(random, 1, 5); boolean object = random.nextBoolean(); int dataType = -1; if (object == false) { @@ -187,7 +196,7 @@ private static void addFields(Random random, XContentBuilder builder, int curren } private static int randomDataType(Random random) { - return RandomNumbers.randomIntBetween(random, 0, 3); + return randomIntBetween(random, 0, 3); } private static Object randomFieldValue(Random random, int dataType) { @@ -204,4 +213,41 @@ private static Object randomFieldValue(Random random, int dataType) { throw new UnsupportedOperationException(); } } + + /** + * Returns a random {@link ShardInfo} object with on or more {@link ShardInfo.Failure} if requested. + * + * @param random Random generator + * @param failures If true, the {@link ShardInfo} will have random failures + * @return a random {@link ShardInfo} + */ + public static ShardInfo randomShardInfo(Random random, boolean failures) { + int total = randomIntBetween(random, 1, 10); + if (failures == false) { + return new ShardInfo(total, total); + } + + int successful = randomIntBetween(random, 1, total); + return new ShardInfo(total, successful, randomShardInfoFailures(random, Math.max(1, (total - successful)))); + } + + public static ShardInfo.Failure[] randomShardInfoFailures(Random random, int nbFailures) { + List randomFailures = new ArrayList<>(nbFailures); + for (int i = 0; i < nbFailures; i++) { + randomFailures.add(randomShardInfoFailure(random)); + } + return randomFailures.toArray(new ShardInfo.Failure[nbFailures]); + } + + public static ShardInfo.Failure randomShardInfoFailure(Random random) { + String index = randomAsciiOfLength(random, 5); + String indexUuid = randomAsciiOfLength(random, 5); + int shardId = randomIntBetween(random, 1, 10); + ShardId shard = new ShardId(index, indexUuid, shardId); + RestStatus restStatus = randomFrom(RestStatus.values()); + Exception exception = RandomPicks.randomFrom(random, Arrays.asList(new IndexShardRecoveringException(shard), + new ElasticsearchException(new IllegalArgumentException("Argument is wrong")), + new RoutingMissingException(index, randomAsciiOfLength(random, 5), randomAsciiOfLength(random, 5)))); + return new ShardInfo.Failure(shard, randomAsciiOfLength(random, 3), exception, restStatus, random.nextBoolean()); + } } From fdf08305baa03ffc52d688d34ecd3992f519adf4 Mon Sep 17 00:00:00 2001 From: Tanguy Leroux Date: Tue, 10 Jan 2017 15:23:05 +0100 Subject: [PATCH 2/2] Update after Luca review --- .../org/elasticsearch/action/index/IndexResponseTests.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/test/java/org/elasticsearch/action/index/IndexResponseTests.java b/core/src/test/java/org/elasticsearch/action/index/IndexResponseTests.java index 736ebe16ba372..326b44116f8d8 100644 --- a/core/src/test/java/org/elasticsearch/action/index/IndexResponseTests.java +++ b/core/src/test/java/org/elasticsearch/action/index/IndexResponseTests.java @@ -160,8 +160,7 @@ private static IndexResponse randomIndexResponse() { boolean created = randomBoolean(); IndexResponse indexResponse = new IndexResponse(shardId, type, id, seqNo, version, created); - //indexResponse.setForcedRefresh(randomBoolean()); - indexResponse.setForcedRefresh(true); + indexResponse.setForcedRefresh(randomBoolean()); indexResponse.setShardInfo(RandomObjects.randomShardInfo(random(), randomBoolean())); return indexResponse; }