/* * 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.gateway; import org.elasticsearch.Version; import org.elasticsearch.cluster.ClusterChangedEvent; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ESAllocationTestCase; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexTemplateMetaData; import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.MetaDataIndexUpgradeService; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNodes; import org.elasticsearch.cluster.routing.RoutingTable; import org.elasticsearch.cluster.routing.allocation.AllocationService; import org.elasticsearch.cluster.routing.allocation.decider.ClusterRebalanceAllocationDecider; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.Index; import org.elasticsearch.plugins.MetaDataUpgrader; import org.elasticsearch.test.TestCustomMetaData; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.Iterator; import java.util.Set; import static java.util.Collections.emptySet; import static org.elasticsearch.cluster.routing.ShardRoutingState.INITIALIZING; import static org.hamcrest.Matchers.equalTo; /** * Test IndexMetaState for master and data only nodes return correct list of indices to write * There are many parameters: * - meta state is not in memory * - meta state is in memory with old version/ new version * - meta state is in memory with new version * - version changed in cluster state event/ no change * - node is data only node * - node is master eligible * for data only nodes: shard initializing on shard */ public class GatewayMetaStateTests extends ESAllocationTestCase { ClusterChangedEvent generateEvent(boolean initializing, boolean versionChanged, boolean masterEligible) { //ridiculous settings to make sure we don't run into uninitialized because fo default AllocationService strategy = createAllocationService(Settings.builder() .put("cluster.routing.allocation.node_concurrent_recoveries", 100) .put(ClusterRebalanceAllocationDecider.CLUSTER_ROUTING_ALLOCATION_ALLOW_REBALANCE_SETTING.getKey(), "always") .put("cluster.routing.allocation.cluster_concurrent_rebalance", 100) .put("cluster.routing.allocation.node_initial_primaries_recoveries", 100) .build()); ClusterState newClusterState, previousClusterState; MetaData metaDataOldClusterState = MetaData.builder() .put(IndexMetaData.builder("test").settings(settings(Version.CURRENT)).numberOfShards(5).numberOfReplicas(2)) .build(); RoutingTable routingTableOldClusterState = RoutingTable.builder() .addAsNew(metaDataOldClusterState.index("test")) .build(); // assign all shards ClusterState init = ClusterState.builder(org.elasticsearch.cluster.ClusterName.CLUSTER_NAME_SETTING.getDefault(Settings.EMPTY)) .metaData(metaDataOldClusterState) .routingTable(routingTableOldClusterState) .nodes(generateDiscoveryNodes(masterEligible)) .build(); // new cluster state will have initializing shards on node 1 RoutingTable routingTableNewClusterState = strategy.reroute(init, "reroute").routingTable(); if (initializing == false) { // pretend all initialized, nothing happened ClusterState temp = ClusterState.builder(init).routingTable(routingTableNewClusterState) .metaData(metaDataOldClusterState).build(); routingTableNewClusterState = strategy.applyStartedShards(temp, temp.getRoutingNodes().shardsWithState(INITIALIZING)) .routingTable(); routingTableOldClusterState = routingTableNewClusterState; } else { // nothing to do, we have one routing table with unassigned and one with initializing } // create new meta data either with version changed or not MetaData metaDataNewClusterState = MetaData.builder() .put(init.metaData().index("test"), versionChanged) .build(); // create the cluster states with meta data and routing tables as computed before previousClusterState = ClusterState.builder(init) .metaData(metaDataOldClusterState) .routingTable(routingTableOldClusterState) .nodes(generateDiscoveryNodes(masterEligible)) .build(); newClusterState = ClusterState.builder(previousClusterState).routingTable(routingTableNewClusterState) .metaData(metaDataNewClusterState).version(previousClusterState.getVersion() + 1).build(); ClusterChangedEvent event = new ClusterChangedEvent("test", newClusterState, previousClusterState); assertThat(event.state().version(), equalTo(event.previousState().version() + 1)); return event; } ClusterChangedEvent generateCloseEvent(boolean masterEligible) { //ridiculous settings to make sure we don't run into uninitialized because fo default AllocationService strategy = createAllocationService(Settings.builder() .put("cluster.routing.allocation.node_concurrent_recoveries", 100) .put(ClusterRebalanceAllocationDecider.CLUSTER_ROUTING_ALLOCATION_ALLOW_REBALANCE_SETTING.getKey(), "always") .put("cluster.routing.allocation.cluster_concurrent_rebalance", 100) .put("cluster.routing.allocation.node_initial_primaries_recoveries", 100) .build()); ClusterState newClusterState, previousClusterState; MetaData metaDataIndexCreated = MetaData.builder() .put(IndexMetaData.builder("test").settings(settings(Version.CURRENT)).numberOfShards(5).numberOfReplicas(2)) .build(); RoutingTable routingTableIndexCreated = RoutingTable.builder() .addAsNew(metaDataIndexCreated.index("test")) .build(); // assign all shards ClusterState init = ClusterState.builder(org.elasticsearch.cluster.ClusterName.CLUSTER_NAME_SETTING.getDefault(Settings.EMPTY)) .metaData(metaDataIndexCreated) .routingTable(routingTableIndexCreated) .nodes(generateDiscoveryNodes(masterEligible)) .build(); RoutingTable routingTableInitializing = strategy.reroute(init, "reroute").routingTable(); ClusterState temp = ClusterState.builder(init).routingTable(routingTableInitializing).build(); RoutingTable routingTableStarted = strategy.applyStartedShards(temp, temp.getRoutingNodes().shardsWithState(INITIALIZING)) .routingTable(); // create new meta data either with version changed or not MetaData metaDataStarted = MetaData.builder() .put(init.metaData().index("test"), true) .build(); // create the cluster states with meta data and routing tables as computed before MetaData metaDataClosed = MetaData.builder() .put(IndexMetaData.builder("test").settings(settings(Version.CURRENT)).state(IndexMetaData.State.CLOSE) .numberOfShards(5).numberOfReplicas(2)).version(metaDataStarted.version() + 1) .build(); previousClusterState = ClusterState.builder(init) .metaData(metaDataStarted) .routingTable(routingTableStarted) .nodes(generateDiscoveryNodes(masterEligible)) .build(); newClusterState = ClusterState.builder(previousClusterState) .routingTable(routingTableIndexCreated) .metaData(metaDataClosed) .version(previousClusterState.getVersion() + 1).build(); ClusterChangedEvent event = new ClusterChangedEvent("test", newClusterState, previousClusterState); assertThat(event.state().version(), equalTo(event.previousState().version() + 1)); return event; } private DiscoveryNodes.Builder generateDiscoveryNodes(boolean masterEligible) { Set<DiscoveryNode.Role> dataOnlyRoles = Collections.singleton(DiscoveryNode.Role.DATA); return DiscoveryNodes.builder().add(newNode("node1", masterEligible ? MASTER_DATA_ROLES : dataOnlyRoles)) .add(newNode("master_node", MASTER_DATA_ROLES)).localNodeId("node1").masterNodeId(masterEligible ? "node1" : "master_node"); } public void assertState(ClusterChangedEvent event, boolean stateInMemory, boolean expectMetaData) throws Exception { MetaData inMemoryMetaData = null; Set<Index> oldIndicesList = emptySet(); if (stateInMemory) { inMemoryMetaData = event.previousState().metaData(); oldIndicesList = GatewayMetaState.getRelevantIndices(event.previousState(), event.previousState(), oldIndicesList); } Set<Index> newIndicesList = GatewayMetaState.getRelevantIndices(event.state(),event.previousState(), oldIndicesList); // third, get the actual write info Iterator<GatewayMetaState.IndexMetaWriteInfo> indices = GatewayMetaState.resolveStatesToBeWritten(oldIndicesList, newIndicesList, inMemoryMetaData, event.state().metaData()).iterator(); if (expectMetaData) { assertThat(indices.hasNext(), equalTo(true)); assertThat(indices.next().getNewMetaData().getIndex().getName(), equalTo("test")); assertThat(indices.hasNext(), equalTo(false)); } else { assertThat(indices.hasNext(), equalTo(false)); } } public void testVersionChangeIsAlwaysWritten() throws Exception { // test that version changes are always written boolean initializing = randomBoolean(); boolean versionChanged = true; boolean stateInMemory = randomBoolean(); boolean masterEligible = randomBoolean(); boolean expectMetaData = true; ClusterChangedEvent event = generateEvent(initializing, versionChanged, masterEligible); assertState(event, stateInMemory, expectMetaData); } public void testNewShardsAlwaysWritten() throws Exception { // make sure new shards on data only node always written boolean initializing = true; boolean versionChanged = randomBoolean(); boolean stateInMemory = randomBoolean(); boolean masterEligible = false; boolean expectMetaData = true; ClusterChangedEvent event = generateEvent(initializing, versionChanged, masterEligible); assertState(event, stateInMemory, expectMetaData); } public void testAllUpToDateNothingWritten() throws Exception { // make sure state is not written again if we wrote already boolean initializing = false; boolean versionChanged = false; boolean stateInMemory = true; boolean masterEligible = randomBoolean(); boolean expectMetaData = false; ClusterChangedEvent event = generateEvent(initializing, versionChanged, masterEligible); assertState(event, stateInMemory, expectMetaData); } public void testNoWriteIfNothingChanged() throws Exception { boolean initializing = false; boolean versionChanged = false; boolean stateInMemory = true; boolean masterEligible = randomBoolean(); boolean expectMetaData = false; ClusterChangedEvent event = generateEvent(initializing, versionChanged, masterEligible); ClusterChangedEvent newEventWithNothingChanged = new ClusterChangedEvent("test cluster state", event.state(), event.state()); assertState(newEventWithNothingChanged, stateInMemory, expectMetaData); } public void testWriteClosedIndex() throws Exception { // test that the closing of an index is written also on data only node boolean masterEligible = randomBoolean(); boolean expectMetaData = true; boolean stateInMemory = true; ClusterChangedEvent event = generateCloseEvent(masterEligible); assertState(event, stateInMemory, expectMetaData); } public void testAddCustomMetaDataOnUpgrade() throws Exception { MetaData metaData = randomMetaData(); MetaDataUpgrader metaDataUpgrader = new MetaDataUpgrader( Collections.singletonList(customs -> { customs.put(CustomMetaData1.TYPE, new CustomMetaData1("modified_data1")); return customs; }), Collections.emptyList() ); MetaData upgrade = GatewayMetaState.upgradeMetaData(metaData, new MockMetaDataIndexUpgradeService(false), metaDataUpgrader); assertTrue(upgrade != metaData); assertFalse(MetaData.isGlobalStateEquals(upgrade, metaData)); assertNotNull(upgrade.custom(CustomMetaData1.TYPE)); assertThat(((TestCustomMetaData) upgrade.custom(CustomMetaData1.TYPE)).getData(), equalTo("modified_data1")); } public void testRemoveCustomMetaDataOnUpgrade() throws Exception { MetaData metaData = randomMetaData(new CustomMetaData1("data")); MetaDataUpgrader metaDataUpgrader = new MetaDataUpgrader( Collections.singletonList(customs -> { customs.remove(CustomMetaData1.TYPE); return customs; }), Collections.emptyList() ); MetaData upgrade = GatewayMetaState.upgradeMetaData(metaData, new MockMetaDataIndexUpgradeService(false), metaDataUpgrader); assertTrue(upgrade != metaData); assertFalse(MetaData.isGlobalStateEquals(upgrade, metaData)); assertNull(upgrade.custom(CustomMetaData1.TYPE)); } public void testUpdateCustomMetaDataOnUpgrade() throws Exception { MetaData metaData = randomMetaData(new CustomMetaData1("data")); MetaDataUpgrader metaDataUpgrader = new MetaDataUpgrader( Collections.singletonList(customs -> { customs.put(CustomMetaData1.TYPE, new CustomMetaData1("modified_data1")); return customs; }), Collections.emptyList() ); MetaData upgrade = GatewayMetaState.upgradeMetaData(metaData, new MockMetaDataIndexUpgradeService(false), metaDataUpgrader); assertTrue(upgrade != metaData); assertFalse(MetaData.isGlobalStateEquals(upgrade, metaData)); assertNotNull(upgrade.custom(CustomMetaData1.TYPE)); assertThat(((TestCustomMetaData) upgrade.custom(CustomMetaData1.TYPE)).getData(), equalTo("modified_data1")); } public void testUpdateTemplateMetaDataOnUpgrade() throws Exception { MetaData metaData = randomMetaData(); MetaDataUpgrader metaDataUpgrader = new MetaDataUpgrader( Collections.emptyList(), Collections.singletonList( templates -> { templates.put("added_test_template", IndexTemplateMetaData.builder("added_test_template").build()); return templates; } )); MetaData upgrade = GatewayMetaState.upgradeMetaData(metaData, new MockMetaDataIndexUpgradeService(false), metaDataUpgrader); assertTrue(upgrade != metaData); assertFalse(MetaData.isGlobalStateEquals(upgrade, metaData)); assertTrue(upgrade.templates().containsKey("added_test_template")); } public void testNoMetaDataUpgrade() throws Exception { MetaData metaData = randomMetaData(new CustomMetaData1("data")); MetaDataUpgrader metaDataUpgrader = new MetaDataUpgrader(Collections.emptyList(), Collections.emptyList()); MetaData upgrade = GatewayMetaState.upgradeMetaData(metaData, new MockMetaDataIndexUpgradeService(false), metaDataUpgrader); assertTrue(upgrade == metaData); assertTrue(MetaData.isGlobalStateEquals(upgrade, metaData)); for (IndexMetaData indexMetaData : upgrade) { assertTrue(metaData.hasIndexMetaData(indexMetaData)); } } public void testCustomMetaDataValidation() throws Exception { MetaData metaData = randomMetaData(new CustomMetaData1("data")); MetaDataUpgrader metaDataUpgrader = new MetaDataUpgrader(Collections.singletonList( customs -> { throw new IllegalStateException("custom meta data too old"); } ), Collections.emptyList()); try { GatewayMetaState.upgradeMetaData(metaData, new MockMetaDataIndexUpgradeService(false), metaDataUpgrader); } catch (IllegalStateException e) { assertThat(e.getMessage(), equalTo("custom meta data too old")); } } public void testMultipleCustomMetaDataUpgrade() throws Exception { final MetaData metaData; switch (randomIntBetween(0, 2)) { case 0: metaData = randomMetaData(new CustomMetaData1("data1"), new CustomMetaData2("data2")); break; case 1: metaData = randomMetaData(randomBoolean() ? new CustomMetaData1("data1") : new CustomMetaData2("data2")); break; case 2: metaData = randomMetaData(); break; default: throw new IllegalStateException("should never happen"); } MetaDataUpgrader metaDataUpgrader = new MetaDataUpgrader( Arrays.asList( customs -> { customs.put(CustomMetaData1.TYPE, new CustomMetaData1("modified_data1")); return customs; }, customs -> { customs.put(CustomMetaData2.TYPE, new CustomMetaData1("modified_data2")); return customs; } ), Collections.emptyList()); MetaData upgrade = GatewayMetaState.upgradeMetaData(metaData, new MockMetaDataIndexUpgradeService(false), metaDataUpgrader); assertTrue(upgrade != metaData); assertFalse(MetaData.isGlobalStateEquals(upgrade, metaData)); assertNotNull(upgrade.custom(CustomMetaData1.TYPE)); assertThat(((TestCustomMetaData) upgrade.custom(CustomMetaData1.TYPE)).getData(), equalTo("modified_data1")); assertNotNull(upgrade.custom(CustomMetaData2.TYPE)); assertThat(((TestCustomMetaData) upgrade.custom(CustomMetaData2.TYPE)).getData(), equalTo("modified_data2")); for (IndexMetaData indexMetaData : upgrade) { assertTrue(metaData.hasIndexMetaData(indexMetaData)); } } public void testIndexMetaDataUpgrade() throws Exception { MetaData metaData = randomMetaData(); MetaDataUpgrader metaDataUpgrader = new MetaDataUpgrader(Collections.emptyList(), Collections.emptyList()); MetaData upgrade = GatewayMetaState.upgradeMetaData(metaData, new MockMetaDataIndexUpgradeService(true), metaDataUpgrader); assertTrue(upgrade != metaData); assertTrue(MetaData.isGlobalStateEquals(upgrade, metaData)); for (IndexMetaData indexMetaData : upgrade) { assertFalse(metaData.hasIndexMetaData(indexMetaData)); } } public void testCustomMetaDataNoChange() throws Exception { MetaData metaData = randomMetaData(new CustomMetaData1("data")); MetaDataUpgrader metaDataUpgrader = new MetaDataUpgrader(Collections.singletonList(HashMap::new), Collections.singletonList(HashMap::new)); MetaData upgrade = GatewayMetaState.upgradeMetaData(metaData, new MockMetaDataIndexUpgradeService(false), metaDataUpgrader); assertTrue(upgrade == metaData); assertTrue(MetaData.isGlobalStateEquals(upgrade, metaData)); for (IndexMetaData indexMetaData : upgrade) { assertTrue(metaData.hasIndexMetaData(indexMetaData)); } } public void testIndexTemplateValidation() throws Exception { MetaData metaData = randomMetaData(); MetaDataUpgrader metaDataUpgrader = new MetaDataUpgrader( Collections.emptyList(), Collections.singletonList( customs -> { throw new IllegalStateException("template is incompatible"); })); String message = expectThrows(IllegalStateException.class, () -> GatewayMetaState.upgradeMetaData(metaData, new MockMetaDataIndexUpgradeService(false), metaDataUpgrader)).getMessage(); assertThat(message, equalTo("template is incompatible")); } public void testMultipleIndexTemplateUpgrade() throws Exception { final MetaData metaData; switch (randomIntBetween(0, 2)) { case 0: metaData = randomMetaDataWithIndexTemplates("template1", "template2"); break; case 1: metaData = randomMetaDataWithIndexTemplates(randomBoolean() ? "template1" : "template2"); break; case 2: metaData = randomMetaData(); break; default: throw new IllegalStateException("should never happen"); } MetaDataUpgrader metaDataUpgrader = new MetaDataUpgrader( Collections.emptyList(), Arrays.asList( indexTemplateMetaDatas -> { indexTemplateMetaDatas.put("template1", IndexTemplateMetaData.builder("template1").settings( Settings.builder().put(IndexMetaData.INDEX_NUMBER_OF_SHARDS_SETTING.getKey(), 20).build()).build()); return indexTemplateMetaDatas; }, indexTemplateMetaDatas -> { indexTemplateMetaDatas.put("template2", IndexTemplateMetaData.builder("template2").settings( Settings.builder().put(IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.getKey(), 10).build()).build()); return indexTemplateMetaDatas; } )); MetaData upgrade = GatewayMetaState.upgradeMetaData(metaData, new MockMetaDataIndexUpgradeService(false), metaDataUpgrader); assertTrue(upgrade != metaData); assertFalse(MetaData.isGlobalStateEquals(upgrade, metaData)); assertNotNull(upgrade.templates().get("template1")); assertThat(IndexMetaData.INDEX_NUMBER_OF_SHARDS_SETTING.get(upgrade.templates().get("template1").settings()), equalTo(20)); assertNotNull(upgrade.templates().get("template2")); assertThat(IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.get(upgrade.templates().get("template2").settings()), equalTo(10)); for (IndexMetaData indexMetaData : upgrade) { assertTrue(metaData.hasIndexMetaData(indexMetaData)); } } private static class MockMetaDataIndexUpgradeService extends MetaDataIndexUpgradeService { private final boolean upgrade; MockMetaDataIndexUpgradeService(boolean upgrade) { super(Settings.EMPTY, null, null, null, null); this.upgrade = upgrade; } @Override public IndexMetaData upgradeIndexMetaData(IndexMetaData indexMetaData, Version minimumIndexCompatibilityVersion) { return upgrade ? IndexMetaData.builder(indexMetaData).build() : indexMetaData; } } private static class CustomMetaData1 extends TestCustomMetaData { public static final String TYPE = "custom_md_1"; protected CustomMetaData1(String data) { super(data); } @Override public String getWriteableName() { return TYPE; } @Override public EnumSet<MetaData.XContentContext> context() { return EnumSet.of(MetaData.XContentContext.GATEWAY); } } private static class CustomMetaData2 extends TestCustomMetaData { public static final String TYPE = "custom_md_2"; protected CustomMetaData2(String data) { super(data); } @Override public String getWriteableName() { return TYPE; } @Override public EnumSet<MetaData.XContentContext> context() { return EnumSet.of(MetaData.XContentContext.GATEWAY); } } private static MetaData randomMetaData(TestCustomMetaData... customMetaDatas) { MetaData.Builder builder = MetaData.builder(); for (TestCustomMetaData customMetaData : customMetaDatas) { builder.putCustom(customMetaData.getWriteableName(), customMetaData); } for (int i = 0; i < randomIntBetween(1, 5); i++) { builder.put( IndexMetaData.builder(randomAlphaOfLength(10)) .settings(settings(Version.CURRENT)) .numberOfReplicas(randomIntBetween(0, 3)) .numberOfShards(randomIntBetween(1, 5)) ); } return builder.build(); } private static MetaData randomMetaDataWithIndexTemplates(String... templates) { MetaData.Builder builder = MetaData.builder(); for (String template : templates) { IndexTemplateMetaData templateMetaData = IndexTemplateMetaData.builder(template) .settings(settings(Version.CURRENT) .put(IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.getKey(), randomIntBetween(0, 3)) .put(IndexMetaData.INDEX_NUMBER_OF_SHARDS_SETTING.getKey(), randomIntBetween(1, 5))) .build(); builder.put(templateMetaData); } for (int i = 0; i < randomIntBetween(1, 5); i++) { builder.put( IndexMetaData.builder(randomAlphaOfLength(10)) .settings(settings(Version.CURRENT)) .numberOfReplicas(randomIntBetween(0, 3)) .numberOfShards(randomIntBetween(1, 5)) ); } return builder.build(); } }