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..326b44116f8d8 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/action/index/IndexResponseTests.java @@ -0,0 +1,168 @@ +/* + * 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.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()); + } }