/* * 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.cluster; import com.carrotsearch.hppc.cursors.ObjectCursor; import org.elasticsearch.Version; import org.elasticsearch.cluster.block.ClusterBlock; import org.elasticsearch.cluster.block.ClusterBlocks; import org.elasticsearch.cluster.metadata.AliasMetaData; import org.elasticsearch.cluster.metadata.IndexGraveyard; import org.elasticsearch.cluster.metadata.IndexGraveyardTests; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexTemplateMetaData; import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.RepositoriesMetaData; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.snapshots.SnapshotId; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNodes; import org.elasticsearch.cluster.routing.IndexRoutingTable; import org.elasticsearch.cluster.routing.IndexShardRoutingTable; import org.elasticsearch.cluster.routing.RoutingTable; import org.elasticsearch.cluster.routing.ShardRouting; import org.elasticsearch.cluster.routing.ShardRoutingState; import org.elasticsearch.cluster.routing.TestShardRouting; import org.elasticsearch.cluster.routing.UnassignedInfo; import org.elasticsearch.common.UUIDs; import org.elasticsearch.common.collect.ImmutableOpenMap; import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.discovery.DiscoverySettings; import org.elasticsearch.gateway.GatewayService; import org.elasticsearch.index.Index; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.snapshots.Snapshot; import org.elasticsearch.test.ESIntegTestCase; import java.util.Collections; import java.util.List; import java.util.Set; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.Collections.emptySet; import static org.elasticsearch.cluster.metadata.AliasMetaData.newAliasMetaDataBuilder; import static org.elasticsearch.cluster.routing.RandomShardRoutingMutator.randomChange; import static org.elasticsearch.cluster.routing.RandomShardRoutingMutator.randomReason; import static org.elasticsearch.test.VersionUtils.randomVersion; import static org.elasticsearch.test.XContentTestUtils.convertToMap; import static org.elasticsearch.test.XContentTestUtils.differenceBetweenMapsIgnoringArrayOrder; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; @ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.SUITE, numDataNodes = 0, numClientNodes = 0) public class ClusterStateDiffIT extends ESIntegTestCase { public void testClusterStateDiffSerialization() throws Exception { NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry(ClusterModule.getNamedWriteables()); DiscoveryNode masterNode = new DiscoveryNode("master", buildNewFakeTransportAddress(), emptyMap(), emptySet(), Version.CURRENT); DiscoveryNode otherNode = new DiscoveryNode("other", buildNewFakeTransportAddress(), emptyMap(), emptySet(), Version.CURRENT); DiscoveryNodes discoveryNodes = DiscoveryNodes.builder().add(masterNode).add(otherNode).localNodeId(masterNode.getId()).build(); ClusterState clusterState = ClusterState.builder(new ClusterName("test")).nodes(discoveryNodes).build(); ClusterState clusterStateFromDiffs = ClusterState.Builder.fromBytes(ClusterState.Builder.toBytes(clusterState), otherNode, namedWriteableRegistry); int iterationCount = randomIntBetween(10, 300); for (int iteration = 0; iteration < iterationCount; iteration++) { ClusterState previousClusterState = clusterState; ClusterState previousClusterStateFromDiffs = clusterStateFromDiffs; int changesCount = randomIntBetween(1, 4); ClusterState.Builder builder = null; for (int i = 0; i < changesCount; i++) { if (i > 0) { clusterState = builder.build(); } switch (randomInt(4)) { case 0: builder = randomNodes(clusterState); break; case 1: builder = randomRoutingTable(clusterState); break; case 2: builder = randomBlocks(clusterState); break; case 3: builder = randomClusterStateCustoms(clusterState); break; case 4: builder = randomMetaDataChanges(clusterState); break; default: throw new IllegalArgumentException("Shouldn't be here"); } } clusterState = builder.incrementVersion().build(); if (randomIntBetween(0, 10) < 1) { // Update cluster state via full serialization from time to time clusterStateFromDiffs = ClusterState.Builder.fromBytes(ClusterState.Builder.toBytes(clusterState), previousClusterStateFromDiffs.nodes().getLocalNode(), namedWriteableRegistry); } else { // Update cluster states using diffs Diff<ClusterState> diffBeforeSerialization = clusterState.diff(previousClusterState); BytesStreamOutput os = new BytesStreamOutput(); diffBeforeSerialization.writeTo(os); byte[] diffBytes = BytesReference.toBytes(os.bytes()); Diff<ClusterState> diff; try (StreamInput input = StreamInput.wrap(diffBytes)) { StreamInput namedInput = new NamedWriteableAwareStreamInput(input, namedWriteableRegistry); diff = ClusterState.readDiffFrom(namedInput, previousClusterStateFromDiffs.nodes().getLocalNode()); clusterStateFromDiffs = diff.apply(previousClusterStateFromDiffs); } } try { // Check non-diffable elements assertThat(clusterStateFromDiffs.version(), equalTo(clusterState.version())); assertThat(clusterStateFromDiffs.stateUUID(), equalTo(clusterState.stateUUID())); // Check nodes assertThat(clusterStateFromDiffs.nodes().getNodes(), equalTo(clusterState.nodes().getNodes())); assertThat(clusterStateFromDiffs.nodes().getLocalNodeId(), equalTo(previousClusterStateFromDiffs.nodes().getLocalNodeId())); assertThat(clusterStateFromDiffs.nodes().getNodes(), equalTo(clusterState.nodes().getNodes())); for (ObjectCursor<String> node : clusterStateFromDiffs.nodes().getNodes().keys()) { DiscoveryNode node1 = clusterState.nodes().get(node.value); DiscoveryNode node2 = clusterStateFromDiffs.nodes().get(node.value); assertThat(node1.getVersion(), equalTo(node2.getVersion())); assertThat(node1.getAddress(), equalTo(node2.getAddress())); assertThat(node1.getAttributes(), equalTo(node2.getAttributes())); } // Check routing table assertThat(clusterStateFromDiffs.routingTable().version(), equalTo(clusterState.routingTable().version())); assertThat(clusterStateFromDiffs.routingTable().indicesRouting(), equalTo(clusterState.routingTable().indicesRouting())); // Check cluster blocks assertThat(clusterStateFromDiffs.blocks().global(), equalTo(clusterStateFromDiffs.blocks().global())); assertThat(clusterStateFromDiffs.blocks().indices(), equalTo(clusterStateFromDiffs.blocks().indices())); assertThat(clusterStateFromDiffs.blocks().disableStatePersistence(), equalTo(clusterStateFromDiffs.blocks().disableStatePersistence())); // Check metadata assertThat(clusterStateFromDiffs.metaData().version(), equalTo(clusterState.metaData().version())); assertThat(clusterStateFromDiffs.metaData().clusterUUID(), equalTo(clusterState.metaData().clusterUUID())); assertThat(clusterStateFromDiffs.metaData().transientSettings(), equalTo(clusterState.metaData().transientSettings())); assertThat(clusterStateFromDiffs.metaData().persistentSettings(), equalTo(clusterState.metaData().persistentSettings())); assertThat(clusterStateFromDiffs.metaData().indices(), equalTo(clusterState.metaData().indices())); assertThat(clusterStateFromDiffs.metaData().templates(), equalTo(clusterState.metaData().templates())); assertThat(clusterStateFromDiffs.metaData().customs(), equalTo(clusterState.metaData().customs())); assertThat(clusterStateFromDiffs.metaData().equalsAliases(clusterState.metaData()), is(true)); // JSON Serialization test - make sure that both states produce similar JSON assertNull(differenceBetweenMapsIgnoringArrayOrder(convertToMap(clusterStateFromDiffs), convertToMap(clusterState))); // Smoke test - we cannot compare bytes to bytes because some elements might get serialized in different order // however, serialized size should remain the same assertThat(ClusterState.Builder.toBytes(clusterStateFromDiffs).length, equalTo(ClusterState.Builder.toBytes(clusterState).length)); } catch (AssertionError error) { logger.error("Cluster state:\n{}\nCluster state from diffs:\n{}", clusterState.toString(), clusterStateFromDiffs.toString()); throw error; } } logger.info("Final cluster state:[{}]", clusterState.toString()); } /** * Randomly updates nodes in the cluster state */ private ClusterState.Builder randomNodes(ClusterState clusterState) { DiscoveryNodes.Builder nodes = DiscoveryNodes.builder(clusterState.nodes()); List<String> nodeIds = randomSubsetOf(randomInt(clusterState.nodes().getNodes().size() - 1), clusterState.nodes().getNodes().keys().toArray(String.class)); for (String nodeId : nodeIds) { if (nodeId.startsWith("node-")) { nodes.remove(nodeId); if (randomBoolean()) { nodes.add(new DiscoveryNode(nodeId, buildNewFakeTransportAddress(), emptyMap(), emptySet(), randomVersion(random()))); } } } int additionalNodeCount = randomIntBetween(1, 20); for (int i = 0; i < additionalNodeCount; i++) { nodes.add(new DiscoveryNode("node-" + randomAlphaOfLength(10), buildNewFakeTransportAddress(), emptyMap(), emptySet(), randomVersion(random()))); } return ClusterState.builder(clusterState).nodes(nodes); } /** * Randomly updates routing table in the cluster state */ private ClusterState.Builder randomRoutingTable(ClusterState clusterState) { RoutingTable.Builder builder = RoutingTable.builder(clusterState.routingTable()); int numberOfIndices = clusterState.routingTable().indicesRouting().size(); if (numberOfIndices > 0) { List<String> randomIndices = randomSubsetOf(randomInt(numberOfIndices - 1), clusterState.routingTable().indicesRouting().keys().toArray(String.class)); for (String index : randomIndices) { if (randomBoolean()) { builder.remove(index); } else { builder.add(randomChangeToIndexRoutingTable(clusterState.routingTable().indicesRouting().get(index), clusterState.nodes().getNodes().keys().toArray(String.class))); } } } int additionalIndexCount = randomIntBetween(1, 20); for (int i = 0; i < additionalIndexCount; i++) { builder.add(randomIndexRoutingTable("index-" + randomInt(), clusterState.nodes().getNodes().keys().toArray(String.class))); } return ClusterState.builder(clusterState).routingTable(builder.build()); } /** * Randomly updates index routing table in the cluster state */ private IndexRoutingTable randomIndexRoutingTable(String index, String[] nodeIds) { IndexRoutingTable.Builder builder = IndexRoutingTable.builder(new Index(index, "_na_")); int shardCount = randomInt(10); for (int i = 0; i < shardCount; i++) { IndexShardRoutingTable.Builder indexShard = new IndexShardRoutingTable.Builder(new ShardId(index, "_na_", i)); int replicaCount = randomIntBetween(1, 10); Set<String> availableNodeIds = Sets.newHashSet(nodeIds); for (int j = 0; j < replicaCount; j++) { UnassignedInfo unassignedInfo = null; if (randomInt(5) == 1) { unassignedInfo = new UnassignedInfo(randomReason(), randomAlphaOfLength(10)); } if (availableNodeIds.isEmpty()) { break; } String nodeId = randomFrom(availableNodeIds); availableNodeIds.remove(nodeId); indexShard.addShard( TestShardRouting.newShardRouting(index, i, nodeId, null, j == 0, ShardRoutingState.fromValue((byte) randomIntBetween(2, 3)), unassignedInfo)); } builder.addIndexShard(indexShard.build()); } return builder.build(); } /** * Randomly updates index routing table in the cluster state */ private IndexRoutingTable randomChangeToIndexRoutingTable(IndexRoutingTable original, String[] nodes) { IndexRoutingTable.Builder builder = IndexRoutingTable.builder(original.getIndex()); for (ObjectCursor<IndexShardRoutingTable> indexShardRoutingTable : original.shards().values()) { Set<String> availableNodes = Sets.newHashSet(nodes); for (ShardRouting shardRouting : indexShardRoutingTable.value.shards()) { availableNodes.remove(shardRouting.currentNodeId()); if (shardRouting.relocating()) { availableNodes.remove(shardRouting.relocatingNodeId()); } } for (ShardRouting shardRouting : indexShardRoutingTable.value.shards()) { final ShardRouting updatedShardRouting = randomChange(shardRouting, availableNodes); availableNodes.remove(updatedShardRouting.currentNodeId()); if (shardRouting.relocating()) { availableNodes.remove(updatedShardRouting.relocatingNodeId()); } builder.addShard(updatedShardRouting); } } return builder.build(); } /** * Randomly creates or removes cluster blocks */ private ClusterState.Builder randomBlocks(ClusterState clusterState) { ClusterBlocks.Builder builder = ClusterBlocks.builder().blocks(clusterState.blocks()); int globalBlocksCount = clusterState.blocks().global().size(); if (globalBlocksCount > 0) { List<ClusterBlock> blocks = randomSubsetOf(randomInt(globalBlocksCount - 1), clusterState.blocks().global().toArray(new ClusterBlock[globalBlocksCount])); for (ClusterBlock block : blocks) { builder.removeGlobalBlock(block); } } int additionalGlobalBlocksCount = randomIntBetween(1, 3); for (int i = 0; i < additionalGlobalBlocksCount; i++) { builder.addGlobalBlock(randomGlobalBlock()); } return ClusterState.builder(clusterState).blocks(builder); } /** * Returns a random global block */ private ClusterBlock randomGlobalBlock() { switch (randomInt(2)) { case 0: return DiscoverySettings.NO_MASTER_BLOCK_ALL; case 1: return DiscoverySettings.NO_MASTER_BLOCK_WRITES; default: return GatewayService.STATE_NOT_RECOVERED_BLOCK; } } /** * Random cluster state part generator interface. Used by {@link #randomClusterStateParts(ClusterState, String, RandomClusterPart)} * method to update cluster state with randomly generated parts */ private interface RandomClusterPart<T> { /** * Returns list of parts from metadata */ ImmutableOpenMap<String, T> parts(ClusterState clusterState); /** * Puts the part back into metadata */ ClusterState.Builder put(ClusterState.Builder builder, T part); /** * Remove the part from metadata */ ClusterState.Builder remove(ClusterState.Builder builder, String name); /** * Returns a random part with the specified name */ T randomCreate(String name); /** * Makes random modifications to the part */ T randomChange(T part); } /** * Takes an existing cluster state and randomly adds, removes or updates a cluster state part using randomPart generator. * If a new part is added the prefix value is used as a prefix of randomly generated part name. */ private <T> ClusterState randomClusterStateParts(ClusterState clusterState, String prefix, RandomClusterPart<T> randomPart) { ClusterState.Builder builder = ClusterState.builder(clusterState); ImmutableOpenMap<String, T> parts = randomPart.parts(clusterState); int partCount = parts.size(); if (partCount > 0) { List<String> randomParts = randomSubsetOf(randomInt(partCount - 1), randomPart.parts(clusterState).keys().toArray(String.class)); for (String part : randomParts) { if (randomBoolean()) { randomPart.remove(builder, part); } else { randomPart.put(builder, randomPart.randomChange(parts.get(part))); } } } int additionalPartCount = randomIntBetween(1, 20); for (int i = 0; i < additionalPartCount; i++) { String name = randomName(prefix); randomPart.put(builder, randomPart.randomCreate(name)); } return builder.build(); } /** * Makes random metadata changes */ private ClusterState.Builder randomMetaDataChanges(ClusterState clusterState) { MetaData metaData = clusterState.metaData(); int changesCount = randomIntBetween(1, 10); for (int i = 0; i < changesCount; i++) { switch (randomInt(3)) { case 0: metaData = randomMetaDataSettings(metaData); break; case 1: metaData = randomIndices(metaData); break; case 2: metaData = randomTemplates(metaData); break; case 3: metaData = randomMetaDataCustoms(metaData); break; default: throw new IllegalArgumentException("Shouldn't be here"); } } return ClusterState.builder(clusterState).metaData(MetaData.builder(metaData).version(metaData.version() + 1).build()); } /** * Makes random settings changes */ private Settings randomSettings(Settings settings) { Settings.Builder builder = Settings.builder(); if (randomBoolean()) { builder.put(settings); } int settingsCount = randomInt(10); for (int i = 0; i < settingsCount; i++) { builder.put(randomAlphaOfLength(10), randomAlphaOfLength(10)); } return builder.build(); } /** * Randomly updates persistent or transient settings of the given metadata */ private MetaData randomMetaDataSettings(MetaData metaData) { if (randomBoolean()) { return MetaData.builder(metaData).persistentSettings(randomSettings(metaData.persistentSettings())).build(); } else { return MetaData.builder(metaData).transientSettings(randomSettings(metaData.transientSettings())).build(); } } /** * Random metadata part generator */ private interface RandomPart<T> { /** * Returns list of parts from metadata */ ImmutableOpenMap<String, T> parts(MetaData metaData); /** * Puts the part back into metadata */ MetaData.Builder put(MetaData.Builder builder, T part); /** * Remove the part from metadata */ MetaData.Builder remove(MetaData.Builder builder, String name); /** * Returns a random part with the specified name */ T randomCreate(String name); /** * Makes random modifications to the part */ T randomChange(T part); } /** * Takes an existing cluster state and randomly adds, removes or updates a metadata part using randomPart generator. * If a new part is added the prefix value is used as a prefix of randomly generated part name. */ private <T> MetaData randomParts(MetaData metaData, String prefix, RandomPart<T> randomPart) { MetaData.Builder builder = MetaData.builder(metaData); ImmutableOpenMap<String, T> parts = randomPart.parts(metaData); int partCount = parts.size(); if (partCount > 0) { List<String> randomParts = randomSubsetOf(randomInt(partCount - 1), randomPart.parts(metaData).keys().toArray(String.class)); for (String part : randomParts) { if (randomBoolean()) { randomPart.remove(builder, part); } else { randomPart.put(builder, randomPart.randomChange(parts.get(part))); } } } int additionalPartCount = randomIntBetween(1, 20); for (int i = 0; i < additionalPartCount; i++) { String name = randomName(prefix); randomPart.put(builder, randomPart.randomCreate(name)); } return builder.build(); } /** * Randomly add, deletes or updates indices in the metadata */ private MetaData randomIndices(MetaData metaData) { return randomParts(metaData, "index", new RandomPart<IndexMetaData>() { @Override public ImmutableOpenMap<String, IndexMetaData> parts(MetaData metaData) { return metaData.indices(); } @Override public MetaData.Builder put(MetaData.Builder builder, IndexMetaData part) { return builder.put(part, true); } @Override public MetaData.Builder remove(MetaData.Builder builder, String name) { return builder.remove(name); } @Override public IndexMetaData randomCreate(String name) { IndexMetaData.Builder builder = IndexMetaData.builder(name); Settings.Builder settingsBuilder = Settings.builder(); setRandomIndexSettings(random(), settingsBuilder); settingsBuilder.put(randomSettings(Settings.EMPTY)).put(IndexMetaData.SETTING_VERSION_CREATED, randomVersion(random())); builder.settings(settingsBuilder); builder.numberOfShards(randomIntBetween(1, 10)).numberOfReplicas(randomInt(10)); int aliasCount = randomInt(10); for (int i = 0; i < aliasCount; i++) { builder.putAlias(randomAlias()); } return builder.build(); } @Override public IndexMetaData randomChange(IndexMetaData part) { IndexMetaData.Builder builder = IndexMetaData.builder(part); switch (randomIntBetween(0, 2)) { case 0: builder.settings(Settings.builder().put(part.getSettings()).put(randomSettings(Settings.EMPTY))); break; case 1: if (randomBoolean() && part.getAliases().isEmpty() == false) { builder.removeAlias(randomFrom(part.getAliases().keys().toArray(String.class))); } else { builder.putAlias(AliasMetaData.builder(randomAlphaOfLength(10))); } break; case 2: builder.settings(Settings.builder().put(part.getSettings()).put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID())); break; default: throw new IllegalArgumentException("Shouldn't be here"); } return builder.build(); } }); } /** * Randomly adds, deletes or updates index templates in the metadata */ private MetaData randomTemplates(MetaData metaData) { return randomParts(metaData, "template", new RandomPart<IndexTemplateMetaData>() { @Override public ImmutableOpenMap<String, IndexTemplateMetaData> parts(MetaData metaData) { return metaData.templates(); } @Override public MetaData.Builder put(MetaData.Builder builder, IndexTemplateMetaData part) { return builder.put(part); } @Override public MetaData.Builder remove(MetaData.Builder builder, String name) { return builder.removeTemplate(name); } @Override public IndexTemplateMetaData randomCreate(String name) { IndexTemplateMetaData.Builder builder = IndexTemplateMetaData.builder(name); builder.order(randomInt(1000)) .patterns(Collections.singletonList(randomName("temp"))) .settings(randomSettings(Settings.EMPTY)); int aliasCount = randomIntBetween(0, 10); for (int i = 0; i < aliasCount; i++) { builder.putAlias(randomAlias()); } return builder.build(); } @Override public IndexTemplateMetaData randomChange(IndexTemplateMetaData part) { IndexTemplateMetaData.Builder builder = new IndexTemplateMetaData.Builder(part); builder.order(randomInt(1000)); return builder.build(); } }); } /** * Generates random alias */ private AliasMetaData randomAlias() { AliasMetaData.Builder builder = newAliasMetaDataBuilder(randomName("alias")); if (randomBoolean()) { builder.filter(QueryBuilders.termQuery("test", randomRealisticUnicodeOfCodepointLength(10)).toString()); } if (randomBoolean()) { builder.routing(randomAlphaOfLength(10)); } return builder.build(); } /** * Randomly adds, deletes or updates repositories in the metadata */ private MetaData randomMetaDataCustoms(final MetaData metaData) { return randomParts(metaData, "custom", new RandomPart<MetaData.Custom>() { @Override public ImmutableOpenMap<String, MetaData.Custom> parts(MetaData metaData) { return metaData.customs(); } @Override public MetaData.Builder put(MetaData.Builder builder, MetaData.Custom part) { return builder.putCustom(part.getWriteableName(), part); } @Override public MetaData.Builder remove(MetaData.Builder builder, String name) { if (IndexGraveyard.TYPE.equals(name)) { // there must always be at least an empty graveyard return builder.indexGraveyard(IndexGraveyard.builder().build()); } else { return builder.removeCustom(name); } } @Override public MetaData.Custom randomCreate(String name) { if (randomBoolean()) { return new RepositoriesMetaData(); } else { return IndexGraveyardTests.createRandom(); } } @Override public MetaData.Custom randomChange(MetaData.Custom part) { return part; } }); } /** * Randomly adds, deletes or updates in-progress snapshot and restore records in the cluster state */ private ClusterState.Builder randomClusterStateCustoms(final ClusterState clusterState) { return ClusterState.builder(randomClusterStateParts(clusterState, "custom", new RandomClusterPart<ClusterState.Custom>() { @Override public ImmutableOpenMap<String, ClusterState.Custom> parts(ClusterState clusterState) { return clusterState.customs(); } @Override public ClusterState.Builder put(ClusterState.Builder builder, ClusterState.Custom part) { return builder.putCustom(part.getWriteableName(), part); } @Override public ClusterState.Builder remove(ClusterState.Builder builder, String name) { return builder.removeCustom(name); } @Override public ClusterState.Custom randomCreate(String name) { switch (randomIntBetween(0, 1)) { case 0: return new SnapshotsInProgress(new SnapshotsInProgress.Entry( new Snapshot(randomName("repo"), new SnapshotId(randomName("snap"), UUIDs.randomBase64UUID())), randomBoolean(), randomBoolean(), SnapshotsInProgress.State.fromValue((byte) randomIntBetween(0, 6)), Collections.emptyList(), Math.abs(randomLong()), (long) randomIntBetween(0, 1000), ImmutableOpenMap.of())); case 1: return new RestoreInProgress(new RestoreInProgress.Entry( new Snapshot(randomName("repo"), new SnapshotId(randomName("snap"), UUIDs.randomBase64UUID())), RestoreInProgress.State.fromValue((byte) randomIntBetween(0, 3)), emptyList(), ImmutableOpenMap.of())); default: throw new IllegalArgumentException("Shouldn't be here"); } } @Override public ClusterState.Custom randomChange(ClusterState.Custom part) { return part; } })); } /** * Generates a random name that starts with the given prefix */ private String randomName(String prefix) { return prefix + UUIDs.randomBase64UUID(random()); } }