/* * 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.test; import com.carrotsearch.randomizedtesting.generators.RandomPicks; import com.carrotsearch.randomizedtesting.generators.RandomStrings; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.support.replication.ReplicationResponse.ShardInfo; import org.elasticsearch.action.support.replication.ReplicationResponse.ShardInfo.Failure; import org.elasticsearch.cluster.block.ClusterBlockException; 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.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.discovery.DiscoverySettings; import org.elasticsearch.index.shard.IndexShardRecoveringException; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.shard.ShardNotFoundException; import org.elasticsearch.rest.RestStatus; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; 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 java.util.Collections.singleton; import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_UUID_NA_VALUE; import static org.elasticsearch.test.ESTestCase.randomFrom; public final class RandomObjects { private RandomObjects() { } /** * Returns a tuple containing random stored field values and their corresponding expected values once printed out * via {@link org.elasticsearch.common.xcontent.ToXContent#toXContent(XContentBuilder, ToXContent.Params)} and parsed back via * {@link org.elasticsearch.common.xcontent.XContentParser#objectText()}. * Generates values based on what can get printed out. Stored fields values are retrieved from lucene and converted via * {@link org.elasticsearch.index.mapper.MappedFieldType#valueForDisplay(Object)} to either strings, numbers or booleans. * * @param random Random generator * @param xContentType the content type, used to determine what the expected values are for float numbers. */ public static Tuple<List<Object>, List<Object>> randomStoredFieldValues(Random random, XContentType xContentType) { int numValues = randomIntBetween(random, 1, 5); List<Object> originalValues = new ArrayList<>(); List<Object> expectedParsedValues = new ArrayList<>(); int dataType = randomIntBetween(random, 0, 8); for (int i = 0; i < numValues; i++) { switch(dataType) { case 0: long randomLong = random.nextLong(); originalValues.add(randomLong); expectedParsedValues.add(randomLong); break; case 1: int randomInt = random.nextInt(); originalValues.add(randomInt); expectedParsedValues.add(randomInt); break; case 2: Short randomShort = (short) random.nextInt(); originalValues.add(randomShort); expectedParsedValues.add(randomShort.intValue()); break; case 3: Byte randomByte = (byte)random.nextInt(); originalValues.add(randomByte); expectedParsedValues.add(randomByte.intValue()); break; case 4: double randomDouble = random.nextDouble(); originalValues.add(randomDouble); expectedParsedValues.add(randomDouble); break; case 5: Float randomFloat = random.nextFloat(); originalValues.add(randomFloat); if (xContentType == XContentType.CBOR) { //with CBOR we get back a float expectedParsedValues.add(randomFloat); } else if (xContentType == XContentType.SMILE) { //with SMILE we get back a double expectedParsedValues.add(randomFloat.doubleValue()); } else { //with JSON AND YAML we get back a double, but with float precision. expectedParsedValues.add(Double.parseDouble(randomFloat.toString())); } break; case 6: boolean randomBoolean = random.nextBoolean(); originalValues.add(randomBoolean); expectedParsedValues.add(randomBoolean); break; case 7: String randomString = random.nextBoolean() ? RandomStrings.randomAsciiOfLengthBetween(random, 3, 10 ) : randomUnicodeOfLengthBetween(random, 3, 10); originalValues.add(randomString); expectedParsedValues.add(randomString); break; case 8: byte[] randomBytes = RandomStrings.randomUnicodeOfLengthBetween(random, 10, 50).getBytes(StandardCharsets.UTF_8); BytesArray randomBytesArray = new BytesArray(randomBytes); originalValues.add(randomBytesArray); if (xContentType == XContentType.JSON || xContentType == XContentType.YAML) { //JSON and YAML write the base64 format expectedParsedValues.add(Base64.getEncoder().encodeToString(randomBytes)); } else { //SMILE and CBOR write the original bytes as they support binary format expectedParsedValues.add(randomBytesArray); } break; default: throw new UnsupportedOperationException(); } } return Tuple.tuple(originalValues, expectedParsedValues); } /** * Returns a random source containing a random number of fields, objects and array, with maximum depth 5. * * @param random Random generator */ public static BytesReference randomSource(Random random) { //the source can be stored in any format and eventually converted when retrieved depending on the format of the response return randomSource(random, RandomPicks.randomFrom(random, XContentType.values())); } /** * Returns a random source in a given XContentType containing a random number of fields, objects and array, with maximum depth 5. * The minimum number of fields per object is 1. * * @param random Random generator */ public static BytesReference randomSource(Random random, XContentType xContentType) { return randomSource(random, xContentType, 1); } /** * Returns a random source in a given XContentType containing a random number of fields, objects and array, with maximum depth 5. * The minimum number of fields per object is provided as an argument. * * @param random Random generator */ public static BytesReference randomSource(Random random, XContentType xContentType, int minNumFields) { try (XContentBuilder builder = XContentFactory.contentBuilder(xContentType)) { builder.startObject(); addFields(random, builder, minNumFields, 0); builder.endObject(); return builder.bytes(); } catch(IOException e) { throw new RuntimeException(e); } } /** * Randomly adds fields, objects, or arrays to the provided builder. The maximum depth is 5. */ private static void addFields(Random random, XContentBuilder builder, int minNumFields, int currentDepth) throws IOException { int numFields = randomIntBetween(random, minNumFields, 10); for (int i = 0; i < numFields; i++) { if (currentDepth < 5 && random.nextBoolean()) { if (random.nextBoolean()) { builder.startObject(RandomStrings.randomAsciiOfLengthBetween(random, 6, 10)); addFields(random, builder, minNumFields, currentDepth + 1); builder.endObject(); } else { builder.startArray(RandomStrings.randomAsciiOfLengthBetween(random, 6, 10)); int numElements = randomIntBetween(random, 1, 5); boolean object = random.nextBoolean(); int dataType = -1; if (object == false) { dataType = randomDataType(random); } for (int j = 0; j < numElements; j++) { if (object) { builder.startObject(); addFields(random, builder, minNumFields, 5); builder.endObject(); } else { builder.value(randomFieldValue(random, dataType)); } } builder.endArray(); } } else { builder.field(RandomStrings.randomAsciiOfLengthBetween(random, 6, 10), randomFieldValue(random, randomDataType(random))); } } } private static int randomDataType(Random random) { return randomIntBetween(random, 0, 3); } private static Object randomFieldValue(Random random, int dataType) { switch(dataType) { case 0: return RandomStrings.randomAsciiOfLengthBetween(random, 3, 10); case 1: return RandomStrings.randomAsciiOfLengthBetween(random, 3, 10); case 2: return random.nextLong(); case 3: return random.nextDouble(); default: throw new UnsupportedOperationException(); } } /** * Returns a tuple that contains a randomized {@link ShardInfo} value (left side) and its corresponding * value (right side) after it has been printed out as a {@link ToXContent} and parsed back using a parsing * method like {@link ShardInfo#fromXContent(XContentParser)}. The ShardInfo randomly contains shard failures. * * @param random Random generator */ public static Tuple<ShardInfo, ShardInfo> randomShardInfo(Random random) { return randomShardInfo(random, random.nextBoolean()); } /** * Returns a tuple that contains a randomized {@link ShardInfo} value (left side) and its corresponding * value (right side) after it has been printed out as a {@link ToXContent} and parsed back using a parsing * method like {@link ShardInfo#fromXContent(XContentParser)}. A `withShardFailures` parameter indicates if * the randomized ShardInfo must or must not contain shard failures. * * @param random Random generator * @param withShardFailures indicates if the generated ShardInfo must contain shard failures */ public static Tuple<ShardInfo, ShardInfo> randomShardInfo(Random random, boolean withShardFailures) { int total = randomIntBetween(random, 1, 10); if (withShardFailures == false) { return Tuple.tuple(new ShardInfo(total, total), new ShardInfo(total, total)); } int successful = randomIntBetween(random, 1, Math.max(1, (total - 1))); int failures = Math.max(1, (total - successful)); Failure[] actualFailures = new Failure[failures]; Failure[] expectedFailures = new Failure[failures]; for (int i = 0; i < failures; i++) { Tuple<Failure, Failure> failure = randomShardInfoFailure(random); actualFailures[i] = failure.v1(); expectedFailures[i] = failure.v2(); } return Tuple.tuple(new ShardInfo(total, successful, actualFailures), new ShardInfo(total, successful, expectedFailures)); } /** * Returns a tuple that contains a randomized {@link Failure} value (left side) and its corresponding * value (right side) after it has been printed out as a {@link ToXContent} and parsed back using a parsing * method like {@link ShardInfo.Failure#fromXContent(XContentParser)}. * * @param random Random generator */ private static Tuple<Failure, Failure> randomShardInfoFailure(Random random) { String index = randomAsciiOfLength(random, 5); String indexUuid = randomAsciiOfLength(random, 5); int shardId = randomIntBetween(random, 1, 10); String nodeId = randomAsciiOfLength(random, 5); RestStatus status = randomFrom(random, RestStatus.INTERNAL_SERVER_ERROR, RestStatus.FORBIDDEN, RestStatus.NOT_FOUND); boolean primary = random.nextBoolean(); ShardId shard = new ShardId(index, indexUuid, shardId); Exception actualException; ElasticsearchException expectedException; int type = randomIntBetween(random, 0, 3); switch (type) { case 0: actualException = new ClusterBlockException(singleton(DiscoverySettings.NO_MASTER_BLOCK_WRITES)); expectedException = new ElasticsearchException("Elasticsearch exception [type=cluster_block_exception, " + "reason=blocked by: [SERVICE_UNAVAILABLE/2/no master];]"); break; case 1: actualException = new ShardNotFoundException(shard); expectedException = new ElasticsearchException("Elasticsearch exception [type=shard_not_found_exception, " + "reason=no such shard]"); expectedException.setShard(shard); break; case 2: actualException = new IllegalArgumentException("Closed resource", new RuntimeException("Resource")); expectedException = new ElasticsearchException("Elasticsearch exception [type=illegal_argument_exception, " + "reason=Closed resource]", new ElasticsearchException("Elasticsearch exception [type=runtime_exception, reason=Resource]")); break; case 3: actualException = new IndexShardRecoveringException(shard); expectedException = new ElasticsearchException("Elasticsearch exception [type=index_shard_recovering_exception, " + "reason=CurrentState[RECOVERING] Already recovering]"); expectedException.setShard(shard); break; default: throw new UnsupportedOperationException("No randomized exceptions generated for type [" + type + "]"); } Failure actual = new Failure(shard, nodeId, actualException, status, primary); Failure expected = new Failure(new ShardId(index, INDEX_UUID_NA_VALUE, shardId), nodeId, expectedException, status, primary); return Tuple.tuple(actual, expected); } }