diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/AliasStep.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/AliasStep.java new file mode 100644 index 00000000000..5ed902ac9e6 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/AliasStep.java @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.core.indexlifecycle; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; +import org.elasticsearch.client.Client; +import org.elasticsearch.cluster.metadata.IndexMetaData; + +public class AliasStep extends AsyncActionStep { + public static final String NAME = "aliases"; + + public AliasStep(StepKey key, StepKey nextStepKey, Client client) { + super(key, nextStepKey, client); + } + + @Override + public void performAction(IndexMetaData indexMetaData, Listener listener) { + // get source index + String index = indexMetaData.getIndex().getName(); + // get target shrink index + String targetIndexName = ShrinkStep.SHRUNKEN_INDEX_PREFIX + index; + + IndicesAliasesRequest aliasesRequest = new IndicesAliasesRequest() + .addAliasAction(IndicesAliasesRequest.AliasActions.removeIndex().index(index)) + .addAliasAction(IndicesAliasesRequest.AliasActions.add().index(targetIndexName).alias(index)); + + getClient().admin().indices().aliases(aliasesRequest, ActionListener.wrap(response -> + listener.onResponse(true), listener::onFailure)); + } +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/EnoughShardsWaitStep.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/EnoughShardsWaitStep.java new file mode 100644 index 00000000000..6606a94ab8d --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/EnoughShardsWaitStep.java @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.core.indexlifecycle; + +import org.elasticsearch.action.support.ActiveShardCount; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.index.Index; + +public class EnoughShardsWaitStep extends ClusterStateWaitStep { + public static final String NAME = "enough-shards-allocated"; + private final int numberOfShards; + + public EnoughShardsWaitStep(StepKey key, StepKey nextStepKey, int numberOfShards) { + super(key, nextStepKey); + this.numberOfShards = numberOfShards; + } + + public int getNumberOfShards() { + return numberOfShards; + } + + @Override + public boolean isConditionMet(Index index, ClusterState clusterState) { + // We only want to make progress if all shards are active + return clusterState.metaData().index(index).getNumberOfShards() == numberOfShards && + ActiveShardCount.ALL.enoughShardsActive(clusterState, index.getName()); + } +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/ShrinkAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/ShrinkAction.java index 005a140f44d..990ee872d72 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/ShrinkAction.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/ShrinkAction.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.core.indexlifecycle; import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; import org.elasticsearch.client.Client; +import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.StreamInput; @@ -14,6 +15,8 @@ import org.elasticsearch.common.io.stream.StreamOutput; 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.xpack.core.indexlifecycle.Step.StepKey; import java.io.IOException; import java.util.Arrays; @@ -27,7 +30,6 @@ public class ShrinkAction implements LifecycleAction { public static final String NAME = "shrink"; public static final ParseField NUMBER_OF_SHARDS_FIELD = new ParseField("number_of_shards"); -// private static final String SHRUNK_INDEX_NAME_PREFIX = "shrunk-"; private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>(NAME, a -> new ShrinkAction((Integer) a[0])); @@ -76,92 +78,15 @@ public class ShrinkAction implements LifecycleAction { @Override public List toSteps(Client client, String phase, Step.StepKey nextStepKey) { -// String shrunkenIndexName = SHRUNK_INDEX_NAME_PREFIX + index.getName(); -// // TODO(talevy): magical node.name to allocate to -// String nodeName = "MAGIC"; -// ClusterStateUpdateStep updateAllocationToOneNode = new ClusterStateUpdateStep( -// "move_to_single_node", NAME, phase, index.getName(), (clusterState) -> { -// IndexMetaData idxMeta = clusterState.metaData().index(index); -// if (idxMeta == null) { -// return clusterState; -// } -// Settings.Builder newSettings = Settings.builder() -// .put(IndexMetaData.INDEX_ROUTING_INCLUDE_GROUP_PREFIX, "") -// .put(IndexMetaData.INDEX_ROUTING_EXCLUDE_GROUP_PREFIX, "") -// .put(IndexMetaData.INDEX_ROUTING_REQUIRE_GROUP_SETTING.getKey() + "_name", nodeName); -// return ClusterState.builder(clusterState) -// .metaData(MetaData.builder(clusterState.metaData()) -// .updateSettings(newSettings.build(), index.getName())).build(); -// }); - -// resizeRequest.getTargetIndexRequest().settings(Settings.builder() -// .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, numberOfShards) -// .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, indexMetaData.getNumberOfReplicas()) -// .put(LifecycleSettings.LIFECYCLE_INDEX_CREATION_DATE, indexMetaData.getCreationDate()) -// .build()); -// indexMetaData.getAliases().values().spliterator().forEachRemaining(aliasMetaDataObjectCursor -> { -// resizeRequest.getTargetIndexRequest().alias(new Alias(aliasMetaDataObjectCursor.value.alias())); -// }); - -// // TODO(talevy): needs access to original index metadata, not just Index -// int numReplicas = -1; -// long lifecycleDate = -1L; -// Settings targetIndexSettings = Settings.builder() -// .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, numberOfShards) -// .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, numReplicas) -// .put(LifecycleSettings.LIFECYCLE_INDEX_CREATION_DATE, lifecycleDate) -// .build(); -// CreateIndexRequest targetIndexRequest = new CreateIndexRequest(shrunkenIndexName, targetIndexSettings); - // TODO(talevy): need access to indexmetadata -// indexMetaData.getAliases().values().spliterator().forEachRemaining(aliasMetaDataObjectCursor -> { -// resizeRequest.getTargetIndexRequest().alias(new Alias(aliasMetaDataObjectCursor.value.alias())); -// }); - -// ClientStep shrinkStep = new ClientStep<>( "segment_count", -// NAME, phase, index.getName(), -// -// client.admin().indices().prepareResizeIndex(index.getName(), shrunkenIndexName).setTargetIndex(targetIndexRequest), -// currentState -> { -// // check that shrunken index was already created, if so, no need to both client -// IndexMetaData shrunkMetaData = currentState.metaData().index(shrunkenIndexName); -// return shrunkMetaData != null && shrunkenIndexName.equals(IndexMetaData.INDEX_SHRINK_SOURCE_NAME -// .get(shrunkMetaData.getSettings())); -// -// }, ResizeResponse::isAcknowledged); -// -// -// ConditionalWaitStep shrunkenIndexIsAllocated = new ConditionalWaitStep("wait_replicas_allocated", NAME, -// phase, index.getName(), (currentState) -> ActiveShardCount.ALL.enoughShardsActive(currentState, index.getName()) ); -// -// ClusterStateUpdateStep deleteAndUpdateAliases = new ClusterStateUpdateStep( -// "delete_this_index_set_aliases_on_shrunken", NAME, phase, index.getName(), (clusterState) -> { -// IndexMetaData idxMeta = clusterState.metaData().index(index); -// if (idxMeta == null) { -// return clusterState; -// } - - // TODO(talevy): expose - MetadataDeleteIndexService.deleteIndices(clusterState, Set.of(index.getName())) - // also, looks like deletes are special CS tasks - // AckedClusterStateUpdateTask, Priority.URGENT - - // 1. delete index - // 2. assign alias to shrunken index - // 3. assign index.lifecycle settings to shrunken index -// return clusterState; -// }); - -// UpdateSettingsStep allocateStep = new UpdateSettingsStep(); -// UpdateSettingsStep waitForAllocation = new UpdateSettingsStep(); -// UpdateSettingsStep allocateStep = new UpdateSettingsStep(); -// Step.StepKey allocateKey = new StepKey(phase, NAME, NAME); -// StepKey allocationRoutedKey = new StepKey(phase, NAME, AllocationRoutedStep.NAME); -// -// Settings.Builder newSettings = Settings.builder(); -// newSettings.put(IndexMetaData.INDEX_ROUTING_REQUIRE_GROUP_SETTING.getKey() + "_name", value)); -// UpdateSettingsStep allocateStep = new UpdateSettingsStep(allocateKey, allocationRoutedKey, client, newSettings.build()); -// AllocationRoutedStep routedCheckStep = new AllocationRoutedStep(allocationRoutedKey, nextStepKey); -// return Arrays.asList(allocateStep, routedCheckStep); - return Arrays.asList(); + StepKey shrinkKey = new StepKey(phase, NAME, ShrinkStep.NAME); + StepKey enoughShardsKey = new StepKey(phase, NAME, EnoughShardsWaitStep.NAME); + StepKey aliasKey = new StepKey(phase, NAME, AliasStep.NAME); + StepKey isShrunkIndexKey = new StepKey(phase, NAME, ShrunkenIndexCheckStep.NAME); + ShrinkStep shrink = new ShrinkStep(shrinkKey, enoughShardsKey, client, numberOfShards); + EnoughShardsWaitStep allocated = new EnoughShardsWaitStep(enoughShardsKey, aliasKey, numberOfShards); + AliasStep aliasSwapAndDelete = new AliasStep(aliasKey, isShrunkIndexKey, client); + ShrunkenIndexCheckStep waitOnShrinkTakeover = new ShrunkenIndexCheckStep(isShrunkIndexKey, nextStepKey); + return Arrays.asList(shrink, allocated, aliasSwapAndDelete, waitOnShrinkTakeover); } @Override diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/ShrinkStep.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/ShrinkStep.java index fb737a34218..631fce1cf2b 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/ShrinkStep.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/ShrinkStep.java @@ -10,37 +10,49 @@ import org.elasticsearch.action.admin.indices.alias.Alias; import org.elasticsearch.action.admin.indices.shrink.ResizeRequest; import org.elasticsearch.client.Client; import org.elasticsearch.cluster.metadata.IndexMetaData; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Settings; import java.util.Objects; public class ShrinkStep extends AsyncActionStep { public static final String NAME = "shrink"; + public static final String SHRUNKEN_INDEX_PREFIX = "shrink-"; - private String shrunkenIndexName; + private int numberOfShards; - public ShrinkStep(StepKey key, StepKey nextStepKey, Client client, String shrunkenIndexName) { + public ShrinkStep(StepKey key, StepKey nextStepKey, Client client, int numberOfShards) { super(key, nextStepKey, client); - this.shrunkenIndexName = shrunkenIndexName; + this.numberOfShards = numberOfShards; } - public String getShrunkenIndexName() { - return shrunkenIndexName; + public int getNumberOfShards() { + return numberOfShards; } @Override public void performAction(IndexMetaData indexMetaData, Listener listener) { + // if operating on the shrunken index, do nothing + Long lifecycleDate = LifecycleSettings.LIFECYCLE_INDEX_CREATION_DATE_SETTING.get(indexMetaData.getSettings()); if (lifecycleDate == null) { throw new IllegalStateException("source index[" + indexMetaData.getIndex().getName() + "] is missing setting[" + LifecycleSettings.LIFECYCLE_INDEX_CREATION_DATE); } + String lifecycle = LifecycleSettings.LIFECYCLE_NAME_SETTING.get(indexMetaData.getSettings()); + String phase = LifecycleSettings.LIFECYCLE_PHASE_SETTING.get(indexMetaData.getSettings()); + String action = LifecycleSettings.LIFECYCLE_ACTION_SETTING.get(indexMetaData.getSettings()); Settings relevantTargetSettings = Settings.builder() - .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, indexMetaData.getNumberOfShards()) + .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, numberOfShards) .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, indexMetaData.getNumberOfReplicas()) .put(LifecycleSettings.LIFECYCLE_INDEX_CREATION_DATE, lifecycleDate) + .put(LifecycleSettings.LIFECYCLE_NAME, lifecycle) + .put(LifecycleSettings.LIFECYCLE_PHASE, phase) + .put(LifecycleSettings.LIFECYCLE_ACTION, action) + .put(LifecycleSettings.LIFECYCLE_STEP, ShrunkenIndexCheckStep.NAME) // skip source-index steps .build(); + String shrunkenIndexName = SHRUNKEN_INDEX_PREFIX + indexMetaData.getIndex().getName(); ResizeRequest resizeRequest = new ResizeRequest(shrunkenIndexName, indexMetaData.getIndex().getName()); indexMetaData.getAliases().values().spliterator().forEachRemaining(aliasMetaDataObjectCursor -> { resizeRequest.getTargetIndexRequest().alias(new Alias(aliasMetaDataObjectCursor.value.alias())); @@ -55,7 +67,7 @@ public class ShrinkStep extends AsyncActionStep { @Override public int hashCode() { - return Objects.hash(super.hashCode(), shrunkenIndexName); + return Objects.hash(super.hashCode(), numberOfShards); } @Override @@ -67,7 +79,7 @@ public class ShrinkStep extends AsyncActionStep { return false; } ShrinkStep other = (ShrinkStep) obj; - return super.equals(obj) && Objects.equals(shrunkenIndexName, other.shrunkenIndexName); + return super.equals(obj) && Objects.equals(numberOfShards, other.numberOfShards); } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/ShrunkenIndexCheckStep.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/ShrunkenIndexCheckStep.java new file mode 100644 index 00000000000..1c60e84d33b --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/ShrunkenIndexCheckStep.java @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.core.indexlifecycle; + +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.metadata.IndexMetaData; +import org.elasticsearch.common.Strings; +import org.elasticsearch.index.Index; + +public class ShrunkenIndexCheckStep extends ClusterStateWaitStep { + public static final String NAME = "is-shrunken-index"; + + public ShrunkenIndexCheckStep(StepKey key, StepKey nextStepKey) { + super(key, nextStepKey); + } + + @Override + public boolean isConditionMet(Index index, ClusterState clusterState) { + String shrunkenIndexSource = IndexMetaData.INDEX_SHRINK_SOURCE_NAME.get( + clusterState.metaData().index(index).getSettings()); + if (Strings.isNullOrEmpty(shrunkenIndexSource)) { + throw new IllegalStateException("step[" + NAME + "] is checking an un-shrunken index[" + index.getName() + "]"); + } + return index.getName().equals(ShrinkStep.SHRUNKEN_INDEX_PREFIX + shrunkenIndexSource); + } +} diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/AliasStepTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/AliasStepTests.java new file mode 100644 index 00000000000..467069f3338 --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/AliasStepTests.java @@ -0,0 +1,172 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.core.indexlifecycle; + +import org.apache.lucene.util.SetOnce; +import org.elasticsearch.Version; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.admin.indices.alias.IndicesAliasesAction; +import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; +import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest.AliasActions; +import org.elasticsearch.action.admin.indices.alias.IndicesAliasesResponse; +import org.elasticsearch.client.AdminClient; +import org.elasticsearch.client.Client; +import org.elasticsearch.client.IndicesAdminClient; +import org.elasticsearch.cluster.metadata.IndexMetaData; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.EqualsHashCodeTestUtils; +import org.elasticsearch.xpack.core.indexlifecycle.AsyncActionStep.Listener; +import org.elasticsearch.xpack.core.indexlifecycle.Step.StepKey; +import org.junit.Before; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.Matchers.equalTo; + +public class AliasStepTests extends ESTestCase { + + private Client client; + + @Before + public void setup() { + client = Mockito.mock(Client.class); + } + + public AliasStep createRandomInstance() { + StepKey stepKey = new StepKey(randomAlphaOfLength(10), randomAlphaOfLength(10), randomAlphaOfLength(10)); + StepKey nextStepKey = new StepKey(randomAlphaOfLength(10), randomAlphaOfLength(10), randomAlphaOfLength(10)); + return new AliasStep(stepKey, nextStepKey, client); + } + + public AliasStep mutateInstance(AliasStep instance) { + StepKey key = instance.getKey(); + StepKey nextKey = instance.getNextStepKey(); + switch (between(0, 1)) { + case 0: + key = new StepKey(key.getPhase(), key.getAction(), key.getName() + randomAlphaOfLength(5)); + break; + case 1: + nextKey = new StepKey(key.getPhase(), key.getAction(), key.getName() + randomAlphaOfLength(5)); + break; + default: + throw new AssertionError("Illegal randomisation branch"); + } + + return new AliasStep(key, nextKey, instance.getClient()); + } + + public void testHashcodeAndEquals() { + EqualsHashCodeTestUtils + .checkEqualsAndHashCode(createRandomInstance(), + instance -> new AliasStep(instance.getKey(), instance.getNextStepKey(), instance.getClient()), + this::mutateInstance); + } + + public void testPerformAction() throws Exception { + IndexMetaData indexMetaData = IndexMetaData.builder(randomAlphaOfLength(10)).settings(settings(Version.CURRENT)) + .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); + AliasStep step = createRandomInstance(); + + String sourceIndex = indexMetaData.getIndex().getName(); + String shrunkenIndex = ShrinkStep.SHRUNKEN_INDEX_PREFIX + sourceIndex; + List expectedAliasActions = Arrays.asList( + IndicesAliasesRequest.AliasActions.removeIndex().index(sourceIndex), + IndicesAliasesRequest.AliasActions.add().index(shrunkenIndex).alias(sourceIndex)); + AdminClient adminClient = Mockito.mock(AdminClient.class); + IndicesAdminClient indicesClient = Mockito.mock(IndicesAdminClient.class); + + Mockito.when(client.admin()).thenReturn(adminClient); + Mockito.when(adminClient.indices()).thenReturn(indicesClient); + Mockito.doAnswer(new Answer() { + + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + IndicesAliasesRequest request = (IndicesAliasesRequest) invocation.getArguments()[0]; + assertThat(request.getAliasActions(), equalTo(expectedAliasActions)); + @SuppressWarnings("unchecked") + ActionListener listener = (ActionListener) invocation.getArguments()[1]; + IndicesAliasesResponse response = IndicesAliasesAction.INSTANCE.newResponse(); + response.readFrom(StreamInput.wrap(new byte[] { 1 })); + listener.onResponse(response); + return null; + } + + }).when(indicesClient).aliases(Mockito.any(), Mockito.any()); + + SetOnce actionCompleted = new SetOnce<>(); + step.performAction(indexMetaData, new Listener() { + + @Override + public void onResponse(boolean complete) { + actionCompleted.set(complete); + } + + @Override + public void onFailure(Exception e) { + throw new AssertionError("Unexpected method call", e); + } + }); + + assertTrue(actionCompleted.get()); + + Mockito.verify(client, Mockito.only()).admin(); + Mockito.verify(adminClient, Mockito.only()).indices(); + Mockito.verify(indicesClient, Mockito.only()).aliases(Mockito.any(), Mockito.any()); + } + + public void testPerformActionFailure() throws Exception { + IndexMetaData indexMetaData = IndexMetaData.builder(randomAlphaOfLength(10)).settings(settings(Version.CURRENT)) + .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); + Exception exception = new RuntimeException(); + AliasStep step = createRandomInstance(); + + AdminClient adminClient = Mockito.mock(AdminClient.class); + IndicesAdminClient indicesClient = Mockito.mock(IndicesAdminClient.class); + + Mockito.when(client.admin()).thenReturn(adminClient); + Mockito.when(adminClient.indices()).thenReturn(indicesClient); + Mockito.doAnswer(new Answer() { + + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + @SuppressWarnings("unchecked") + ActionListener listener = (ActionListener) invocation.getArguments()[1]; + listener.onFailure(exception); + return null; + } + + }).when(indicesClient).aliases(Mockito.any(), Mockito.any()); + + SetOnce exceptionThrown = new SetOnce<>(); + step.performAction(indexMetaData, new Listener() { + + @Override + public void onResponse(boolean complete) { + throw new AssertionError("Unexpected method call"); + } + + @Override + public void onFailure(Exception e) { + assertSame(exception, e); + exceptionThrown.set(true); + } + }); + + assertEquals(true, exceptionThrown.get()); + + Mockito.verify(client, Mockito.only()).admin(); + Mockito.verify(adminClient, Mockito.only()).indices(); + Mockito.verify(indicesClient, Mockito.only()).aliases(Mockito.any(), Mockito.any()); + } + +} diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/EnoughShardsWaitStepTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/EnoughShardsWaitStepTests.java new file mode 100644 index 00000000000..58174f18207 --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/EnoughShardsWaitStepTests.java @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.core.indexlifecycle; + +import org.elasticsearch.Version; +import org.elasticsearch.cluster.ClusterName; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.metadata.IndexMetaData; +import org.elasticsearch.cluster.metadata.MetaData; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.cluster.node.DiscoveryNodes; +import org.elasticsearch.cluster.routing.IndexRoutingTable; +import org.elasticsearch.cluster.routing.RoutingTable; +import org.elasticsearch.cluster.routing.ShardRoutingState; +import org.elasticsearch.cluster.routing.TestShardRouting; +import org.elasticsearch.common.transport.TransportAddress; +import org.elasticsearch.index.Index; +import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.node.Node; +import org.elasticsearch.test.ESTestCase; + +public class EnoughShardsWaitStepTests extends ESTestCase { + + public void testConditionMet() { + int numberOfShards = randomIntBetween(1, 10); + IndexMetaData indexMetadata = IndexMetaData.builder(randomAlphaOfLength(5)) + .settings(settings(Version.CURRENT)) + .numberOfShards(numberOfShards) + .numberOfReplicas(0).build(); + MetaData metaData = MetaData.builder() + .persistentSettings(settings(Version.CURRENT).build()) + .put(IndexMetaData.builder(indexMetadata)) + .build(); + Index index = indexMetadata.getIndex(); + + String nodeId = randomAlphaOfLength(10); + DiscoveryNode masterNode = DiscoveryNode.createLocal(settings(Version.CURRENT) + .put(Node.NODE_MASTER_SETTING.getKey(), true).build(), + new TransportAddress(TransportAddress.META_ADDRESS, 9300), nodeId); + + IndexRoutingTable.Builder builder = IndexRoutingTable.builder(index); + for (int i = 0; i < numberOfShards; i++) { + builder.addShard(TestShardRouting.newShardRouting(new ShardId(index, i), + nodeId, true, ShardRoutingState.STARTED)); + } + ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT) + .metaData(metaData) + .nodes(DiscoveryNodes.builder().localNodeId(nodeId).masterNodeId(nodeId).add(masterNode).build()) + .routingTable(RoutingTable.builder().add(builder.build()).build()).build(); + + EnoughShardsWaitStep step = new EnoughShardsWaitStep(null, null, numberOfShards); + assertTrue(step.isConditionMet(indexMetadata.getIndex(), clusterState)); + } + + public void testConditionNotMetBecauseOfActive() { + IndexMetaData indexMetadata = IndexMetaData.builder(randomAlphaOfLength(5)) + .settings(settings(Version.CURRENT)) + .numberOfShards(1) + .numberOfReplicas(0).build(); + MetaData metaData = MetaData.builder() + .persistentSettings(settings(Version.CURRENT).build()) + .put(IndexMetaData.builder(indexMetadata)) + .build(); + Index index = indexMetadata.getIndex(); + + String nodeId = randomAlphaOfLength(10); + DiscoveryNode masterNode = DiscoveryNode.createLocal(settings(Version.CURRENT) + .put(Node.NODE_MASTER_SETTING.getKey(), true).build(), + new TransportAddress(TransportAddress.META_ADDRESS, 9300), nodeId); + + ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT) + .metaData(metaData) + .nodes(DiscoveryNodes.builder().localNodeId(nodeId).masterNodeId(nodeId).add(masterNode).build()) + .routingTable(RoutingTable.builder() + .add(IndexRoutingTable.builder(index).addShard( + TestShardRouting.newShardRouting(new ShardId(index, 0), + nodeId, true, ShardRoutingState.INITIALIZING))) + .build()) + .build(); + + EnoughShardsWaitStep step = new EnoughShardsWaitStep(null, null, 1); + assertFalse(step.isConditionMet(indexMetadata.getIndex(), clusterState)); + } + + public void testConditionNotMetBecauseOfShardCount() { + int numberOfShards = randomIntBetween(1, 10); + IndexMetaData indexMetadata = IndexMetaData.builder(randomAlphaOfLength(5)) + .settings(settings(Version.CURRENT)) + .numberOfShards(numberOfShards) + .numberOfReplicas(0).build(); + MetaData metaData = MetaData.builder() + .persistentSettings(settings(Version.CURRENT).build()) + .put(IndexMetaData.builder(indexMetadata)) + .build(); + Index index = indexMetadata.getIndex(); + + String nodeId = randomAlphaOfLength(10); + DiscoveryNode masterNode = DiscoveryNode.createLocal(settings(Version.CURRENT) + .put(Node.NODE_MASTER_SETTING.getKey(), true).build(), + new TransportAddress(TransportAddress.META_ADDRESS, 9300), nodeId); + + IndexRoutingTable.Builder builder = IndexRoutingTable.builder(index); + for (int i = 0; i < numberOfShards; i++) { + builder.addShard(TestShardRouting.newShardRouting(new ShardId(index, i), + nodeId, true, ShardRoutingState.INITIALIZING)); + } + ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT) + .metaData(metaData) + .nodes(DiscoveryNodes.builder().localNodeId(nodeId).masterNodeId(nodeId).add(masterNode).build()) + .routingTable(RoutingTable.builder().add(builder.build()).build()).build(); + + EnoughShardsWaitStep step = new EnoughShardsWaitStep(null, null, 1); + assertFalse(step.isConditionMet(indexMetadata.getIndex(), clusterState)); + } +} diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/ShrinkActionTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/ShrinkActionTests.java index 225f49580f2..c5cfa84b207 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/ShrinkActionTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/ShrinkActionTests.java @@ -8,8 +8,10 @@ package org.elasticsearch.xpack.core.indexlifecycle; import org.elasticsearch.common.io.stream.Writeable.Reader; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.test.AbstractSerializingTestCase; +import org.elasticsearch.xpack.core.indexlifecycle.Step.StepKey; import java.io.IOException; +import java.util.List; import static org.hamcrest.Matchers.equalTo; @@ -40,480 +42,26 @@ public class ShrinkActionTests extends AbstractSerializingTestCase assertThat(e.getMessage(), equalTo("[number_of_shards] must be greater than 0")); } -// public void testExecuteSuccessfullyCompleted() { -// String originalIndexName = randomAlphaOfLengthBetween(1, 20); -// Index index = new Index("shrunk-" + originalIndexName, randomAlphaOfLengthBetween(1, 20)); -// ClusterService clusterService = Mockito.mock(ClusterService.class); -// IndexMetaData originalIndexMetaData = IndexMetaData.builder(originalIndexName) -// .settings(settings(Version.CURRENT)).numberOfReplicas(0).numberOfShards(1).build(); -// IndexMetaData indexMetadata = IndexMetaData.builder(index.getName()) -// .settings(settings(Version.CURRENT).put(IndexMetaData.INDEX_SHRINK_SOURCE_NAME_KEY, originalIndexName)) -// .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); -// ImmutableOpenMap.Builder indices = ImmutableOpenMap. builder().fPut(index.getName(), -// indexMetadata).fPut(originalIndexName, originalIndexMetaData); -// ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE).metaData(MetaData.builder().indices(indices.build())) -// .build(); -// Mockito.when(clusterService.state()).thenReturn(clusterState); -// Client client = Mockito.mock(Client.class); -// AdminClient adminClient = Mockito.mock(AdminClient.class); -// IndicesAdminClient indicesClient = Mockito.mock(IndicesAdminClient.class); -// Mockito.when(client.admin()).thenReturn(adminClient); -// Mockito.when(adminClient.indices()).thenReturn(indicesClient); -// -// Mockito.doAnswer(invocationOnMock -> { -// IndicesAliasesRequest request = (IndicesAliasesRequest) invocationOnMock.getArguments()[0]; -// @SuppressWarnings("unchecked") -// ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; -// IndicesAliasesResponse response = IndicesAliasesAction.INSTANCE.newResponse(); -// response.readFrom(StreamInput.wrap(new byte[] { 1 })); -// -// assertThat(request.getAliasActions().size(), equalTo(2)); -// assertThat(request.getAliasActions().get(0).actionType(), equalTo(IndicesAliasesRequest.AliasActions.Type.REMOVE_INDEX)); -// assertThat(request.getAliasActions().get(0).indices(), equalTo(new String[] { originalIndexName })); -// assertThat(request.getAliasActions().get(1).actionType(), equalTo(IndicesAliasesRequest.AliasActions.Type.ADD)); -// assertThat(request.getAliasActions().get(1).indices(), equalTo(new String[] { index.getName() })); -// assertThat(request.getAliasActions().get(1).aliases(), equalTo(new String[] { originalIndexName })); -// -// listener.onResponse(response); -// return null; -// }).when(indicesClient).aliases(any(), any()); -// -// SetOnce actionCompleted = new SetOnce<>(); -// ShrinkAction action = new ShrinkAction(randomIntBetween(1, 10)); -// -// action.execute(index, client, clusterService, new LifecycleAction.Listener() { -// @Override -// public void onSuccess(boolean completed) { -// actionCompleted.set(completed); -// } -// -// @Override -// public void onFailure(Exception e) { -// throw new AssertionError("Unexpected method call", e); -// } -// }); -// -// assertTrue(actionCompleted.get()); -// Mockito.verify(client, Mockito.only()).admin(); -// Mockito.verify(adminClient, Mockito.only()).indices(); -// Mockito.verify(indicesClient, Mockito.only()).aliases(any(), any()); -// } -// -// public void testExecuteAlreadyCompletedAndRunAgain() { -// String originalIndexName = randomAlphaOfLengthBetween(1, 20); -// Index index = new Index("shrunk-" + originalIndexName, randomAlphaOfLengthBetween(1, 20)); -// ClusterService clusterService = Mockito.mock(ClusterService.class); -// IndexMetaData indexMetadata = IndexMetaData.builder(index.getName()) -// .putAlias(AliasMetaData.builder(originalIndexName).build()) -// .settings(settings(Version.CURRENT).put(IndexMetaData.INDEX_SHRINK_SOURCE_NAME_KEY, originalIndexName)) -// .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); -// ImmutableOpenMap.Builder indices = ImmutableOpenMap. builder().fPut(index.getName(), -// indexMetadata); -// ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE).metaData(MetaData.builder().indices(indices.build())) -// .build(); -// Mockito.when(clusterService.state()).thenReturn(clusterState); -// Client client = Mockito.mock(Client.class); -// SetOnce actionCompleted = new SetOnce<>(); -// ShrinkAction action = new ShrinkAction(randomIntBetween(1, 10)); -// -// action.execute(index, client, clusterService, new LifecycleAction.Listener() { -// @Override -// public void onSuccess(boolean completed) { -// actionCompleted.set(completed); -// } -// -// @Override -// public void onFailure(Exception e) { -// throw new AssertionError("Unexpected method call", e); -// } -// }); -// -// assertTrue(actionCompleted.get()); -// Mockito.verify(client, Mockito.never()).admin(); -// } -// -// public void testExecuteOriginalIndexAliasFailure() { -// String originalIndexName = randomAlphaOfLengthBetween(1, 20); -// Index index = new Index("shrunk-" + originalIndexName, randomAlphaOfLengthBetween(1, 20)); -// Index targetIndex = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); -// ClusterService clusterService = Mockito.mock(ClusterService.class); -// IndexMetaData indexMetadata = IndexMetaData.builder(index.getName()) -// .settings(settings(Version.CURRENT).put(IndexMetaData.INDEX_SHRINK_SOURCE_NAME_KEY, originalIndexName)) -// .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); -// ImmutableOpenMap.Builder indices = ImmutableOpenMap. builder().fPut(index.getName(), -// indexMetadata); -// ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE).metaData(MetaData.builder().indices(indices.build())) -// .routingTable(RoutingTable.builder() -// .add(IndexRoutingTable.builder(targetIndex).addShard( -// TestShardRouting.newShardRouting(new ShardId(index, 0), "node1", true, -// ShardRoutingState.STARTED))) -// .build()) -// .build(); -// Mockito.when(clusterService.state()).thenReturn(clusterState); -// Client client = Mockito.mock(Client.class); -// AdminClient adminClient = Mockito.mock(AdminClient.class); -// IndicesAdminClient indicesClient = Mockito.mock(IndicesAdminClient.class); -// Mockito.when(client.admin()).thenReturn(adminClient); -// Mockito.when(adminClient.indices()).thenReturn(indicesClient); -// -// Mockito.doAnswer(invocationOnMock -> { -// @SuppressWarnings("unchecked") -// ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; -// listener.onFailure(new RuntimeException("failed")); -// return null; -// }).when(indicesClient).aliases(any(), any()); -// -// SetOnce onFailureException = new SetOnce<>(); -// ShrinkAction action = new ShrinkAction(randomIntBetween(1, 10)); -// -// action.execute(index, client, clusterService, new LifecycleAction.Listener() { -// @Override -// public void onSuccess(boolean completed) { -// throw new AssertionError("Unexpected method call"); -// } -// -// @Override -// public void onFailure(Exception e) { -// onFailureException.set(e); -// } -// }); -// -// assertThat(onFailureException.get().getMessage(), equalTo("failed")); -// -// Mockito.verify(client, Mockito.only()).admin(); -// Mockito.verify(adminClient, Mockito.only()).indices(); -// Mockito.verify(indicesClient, Mockito.only()).aliases(any(), any()); -// } -// -// public void testExecuteWithIssuedResizeRequest() { -// Index index = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); -// Index targetIndex = new Index("shrunk-" + index.getName(), randomAlphaOfLengthBetween(1, 20)); -// int numberOfShards = randomIntBetween(1, 5); -// int numberOfReplicas = randomIntBetween(1, 5); -// long creationDate = randomNonNegativeLong(); -// ClusterService clusterService = Mockito.mock(ClusterService.class); -// IndexMetaData indexMetadata = IndexMetaData.builder(index.getName()) -// .settings(settings(Version.CURRENT)) -// .putAlias(AliasMetaData.builder("my_alias")) -// .creationDate(creationDate) -// .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(numberOfReplicas).build(); -// ImmutableOpenMap.Builder indices = ImmutableOpenMap. builder().fPut(index.getName(), -// indexMetadata); -// ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE).metaData(MetaData.builder().indices(indices.build())) -// .routingTable(RoutingTable.builder() -// .add(IndexRoutingTable.builder(targetIndex).addShard( -// TestShardRouting.newShardRouting(new ShardId(index, 0), "node1", true, -// ShardRoutingState.STARTED))) -// .build()) -// .build(); -// Mockito.when(clusterService.state()).thenReturn(clusterState); -// Client client = Mockito.mock(Client.class); -// AdminClient adminClient = Mockito.mock(AdminClient.class); -// IndicesAdminClient indicesClient = Mockito.mock(IndicesAdminClient.class); -// Mockito.when(client.admin()).thenReturn(adminClient); -// Mockito.when(adminClient.indices()).thenReturn(indicesClient); -// -// Settings expectedSettings = Settings.builder().put(IndexMetaData.SETTING_BLOCKS_WRITE, true).build(); -// -// Mockito.doAnswer(invocationOnMock -> { -// UpdateSettingsRequest request = (UpdateSettingsRequest) invocationOnMock.getArguments()[0]; -// @SuppressWarnings("unchecked") -// ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; -// UpdateSettingsTestHelper.assertSettingsRequest(request, expectedSettings, index.getName()); -// listener.onResponse(UpdateSettingsTestHelper.createMockResponse(true)); -// return null; -// }).when(indicesClient).updateSettings(any(), any()); -// -// Mockito.doAnswer(invocationOnMock -> { -// ResizeRequest request = (ResizeRequest) invocationOnMock.getArguments()[0]; -// @SuppressWarnings("unchecked") -// ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; -// assertThat(request.getSourceIndex(), equalTo(index.getName())); -// assertThat(request.getTargetIndexRequest().aliases(), equalTo(Collections.singleton(new Alias("my_alias")))); -// assertThat(request.getTargetIndexRequest().settings(), equalTo(Settings.builder() -// .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, numberOfShards) -// .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, numberOfReplicas) -// .put(LifecycleSettings.LIFECYCLE_INDEX_CREATION_DATE, creationDate).build())); -// assertThat(request.getTargetIndexRequest().index(), equalTo(targetIndex.getName())); -// ResizeResponse resizeResponse = ResizeAction.INSTANCE.newResponse(); -// resizeResponse.readFrom(StreamInput.wrap(new byte[] { 1, 1, 1, 1, 1 })); -// listener.onResponse(resizeResponse); -// return null; -// }).when(indicesClient).resizeIndex(any(), any()); -// -// SetOnce actionCompleted = new SetOnce<>(); -// ShrinkAction action = new ShrinkAction(numberOfShards); -// -// action.execute(index, client, clusterService, new LifecycleAction.Listener() { -// @Override -// public void onSuccess(boolean completed) { -// actionCompleted.set(completed); -// } -// -// @Override -// public void onFailure(Exception e) { -// throw new AssertionError("Unexpected method call", e); -// } -// }); -// -// assertFalse(actionCompleted.get()); -// -// Mockito.verify(client, Mockito.atLeast(1)).admin(); -// Mockito.verify(adminClient, Mockito.atLeast(1)).indices(); -// Mockito.verify(indicesClient, Mockito.atLeast(1)).updateSettings(any(), any()); -// Mockito.verify(indicesClient, Mockito.atLeast(1)).resizeIndex(any(), any()); -// } -// -// public void testExecuteWithIssuedResizeRequestFailure() { -// Index index = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); -// Index targetIndex = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); -// ClusterService clusterService = Mockito.mock(ClusterService.class); -// IndexMetaData indexMetadata = IndexMetaData.builder(index.getName()) -// .settings(settings(Version.CURRENT)) -// .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); -// ImmutableOpenMap.Builder indices = ImmutableOpenMap. builder().fPut(index.getName(), -// indexMetadata); -// ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE).metaData(MetaData.builder().indices(indices.build())) -// .routingTable(RoutingTable.builder() -// .add(IndexRoutingTable.builder(targetIndex).addShard( -// TestShardRouting.newShardRouting(new ShardId(index, 0), "node1", true, -// ShardRoutingState.STARTED))) -// .build()) -// .build(); -// Mockito.when(clusterService.state()).thenReturn(clusterState); -// Client client = Mockito.mock(Client.class); -// AdminClient adminClient = Mockito.mock(AdminClient.class); -// IndicesAdminClient indicesClient = Mockito.mock(IndicesAdminClient.class); -// Mockito.when(client.admin()).thenReturn(adminClient); -// Mockito.when(adminClient.indices()).thenReturn(indicesClient); -// -// Mockito.doAnswer(invocationOnMock -> { -// @SuppressWarnings("unchecked") -// ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; -// listener.onResponse(UpdateSettingsTestHelper.createMockResponse(true)); -// return null; -// }).when(indicesClient).updateSettings(any(), any()); -// -// Mockito.doAnswer(invocationOnMock -> { -// @SuppressWarnings("unchecked") -// ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; -// ResizeResponse resizeResponse = ResizeAction.INSTANCE.newResponse(); -// resizeResponse.readFrom(StreamInput.wrap(new byte[] { 0, 1, 1, 1, 1 })); -// listener.onResponse(resizeResponse); -// return null; -// }).when(indicesClient).resizeIndex(any(), any()); -// -// SetOnce exceptionReturned = new SetOnce<>(); -// ShrinkAction action = new ShrinkAction(randomIntBetween(1, 10)); -// -// action.execute(index, client, clusterService, new LifecycleAction.Listener() { -// @Override -// public void onSuccess(boolean completed) { -// throw new AssertionError("Unexpected method call to onSuccess"); -// } -// -// @Override -// public void onFailure(Exception e) { -// exceptionReturned.set(e); -// } -// }); -// -// assertThat(exceptionReturned.get().getMessage(), equalTo("Shrink request failed to be acknowledged")); -// -// Mockito.verify(client, Mockito.atLeast(1)).admin(); -// Mockito.verify(adminClient, Mockito.atLeast(1)).indices(); -// Mockito.verify(indicesClient, Mockito.atLeast(1)).updateSettings(any(), any()); -// Mockito.verify(indicesClient, Mockito.atLeast(1)).resizeIndex(any(), any()); -// } -// -// public void testExecuteWithAllShardsAllocatedAndShrunkenIndexSetting() { -// Index index = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); -// Index targetIndex = new Index("shrunk-" + index.getName(), randomAlphaOfLengthBetween(1, 20)); -// ClusterService clusterService = Mockito.mock(ClusterService.class); -// IndexMetaData indexMetadata = IndexMetaData.builder(index.getName()) -// .settings(settings(Version.CURRENT).put(LifecycleSettings.LIFECYCLE_PHASE, "phase1") -// .put(LifecycleSettings.LIFECYCLE_ACTION, "action1").put(LifecycleSettings.LIFECYCLE_NAME, "test")) -// .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); -// IndexMetaData targetIndexMetaData = IndexMetaData.builder(targetIndex.getName()) -// .settings(settings(Version.CURRENT).put(IndexMetaData.INDEX_SHRINK_SOURCE_NAME_KEY, index.getName())) -// .numberOfShards(randomIntBetween(1, 3)).numberOfReplicas(indexMetadata.getNumberOfReplicas()).build(); -// ImmutableOpenMap.Builder indices = ImmutableOpenMap. builder() -// .fPut(index.getName(), indexMetadata).fPut(targetIndex.getName(), targetIndexMetaData); -// ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE).metaData(MetaData.builder().indices(indices.build())) -// .routingTable(RoutingTable.builder() -// .add(IndexRoutingTable.builder(targetIndex).addShard( -// TestShardRouting.newShardRouting(new ShardId(index, 0), "node1", true, -// ShardRoutingState.STARTED))) -// .build()) -// .build(); -// Mockito.when(clusterService.state()).thenReturn(clusterState); -// Client client = Mockito.mock(Client.class); -// AdminClient adminClient = Mockito.mock(AdminClient.class); -// IndicesAdminClient indicesClient = Mockito.mock(IndicesAdminClient.class); -// Mockito.when(client.admin()).thenReturn(adminClient); -// Mockito.when(adminClient.indices()).thenReturn(indicesClient); -// -// Settings expectedSettings = Settings.builder().put(LifecycleSettings.LIFECYCLE_NAME, "test") -// .put(LifecycleSettings.LIFECYCLE_PHASE, "phase1").put(LifecycleSettings.LIFECYCLE_ACTION, "action1").build(); -// -// Mockito.doAnswer(invocationOnMock -> { -// UpdateSettingsRequest request = (UpdateSettingsRequest) invocationOnMock.getArguments()[0]; -// @SuppressWarnings("unchecked") -// ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; -// UpdateSettingsTestHelper.assertSettingsRequest(request, expectedSettings, targetIndex.getName()); -// listener.onResponse(UpdateSettingsTestHelper.createMockResponse(true)); -// return null; -// }).when(indicesClient).updateSettings(any(), any()); -// -// Mockito.doAnswer(invocationOnMock -> { -// DeleteIndexRequest request = (DeleteIndexRequest) invocationOnMock.getArguments()[0]; -// @SuppressWarnings("unchecked") -// ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; -// assertNotNull(request); -// assertEquals(1, request.indices().length); -// assertEquals(index.getName(), request.indices()[0]); -// listener.onResponse(null); -// return null; -// }).when(indicesClient).delete(any(), any()); -// -// SetOnce actionCompleted = new SetOnce<>(); -// ShrinkAction action = new ShrinkAction(randomIntBetween(1, 10)); -// -// action.execute(index, client, clusterService, new LifecycleAction.Listener() { -// @Override -// public void onSuccess(boolean completed) { -// actionCompleted.set(completed); -// } -// -// @Override -// public void onFailure(Exception e) { -// throw new AssertionError("Unexpected method call", e); -// } -// }); -// -// assertFalse(actionCompleted.get()); -// Mockito.verify(client, Mockito.atLeast(1)).admin(); -// Mockito.verify(adminClient, Mockito.atLeast(1)).indices(); -// Mockito.verify(indicesClient, Mockito.atLeast(1)).updateSettings(any(), any()); -// } -// -// public void testExecuteWithAllShardsAllocatedAndShrunkenIndexConfigured() { -// String lifecycleName = randomAlphaOfLengthBetween(5, 10); -// Index index = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); -// Index targetIndex = new Index("shrunk-" + index.getName(), randomAlphaOfLengthBetween(1, 20)); -// ClusterService clusterService = Mockito.mock(ClusterService.class); -// IndexMetaData indexMetadata = IndexMetaData.builder(index.getName()) -// .settings(settings(Version.CURRENT).put(LifecycleSettings.LIFECYCLE_PHASE, "phase1") -// .put(LifecycleSettings.LIFECYCLE_ACTION, "action1").put(LifecycleSettings.LIFECYCLE_NAME, lifecycleName)) -// .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); -// IndexMetaData targetIndexMetaData = IndexMetaData.builder(targetIndex.getName()) -// .settings(settings(Version.CURRENT) -// .put(IndexMetaData.INDEX_SHRINK_SOURCE_NAME_KEY, index.getName()) -// .put(LifecycleSettings.LIFECYCLE_NAME_SETTING.getKey(), lifecycleName)) -// .numberOfShards(randomIntBetween(1, 3)).numberOfReplicas(indexMetadata.getNumberOfReplicas()).build(); -// ImmutableOpenMap.Builder indices = ImmutableOpenMap. builder() -// .fPut(index.getName(), indexMetadata).fPut(targetIndex.getName(), targetIndexMetaData); -// ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE).metaData(MetaData.builder().indices(indices.build())) -// .routingTable(RoutingTable.builder() -// .add(IndexRoutingTable.builder(targetIndex).addShard( -// TestShardRouting.newShardRouting(new ShardId(index, 0), "node1", true, -// ShardRoutingState.STARTED))) -// .build()) -// .build(); -// Mockito.when(clusterService.state()).thenReturn(clusterState); -// Client client = Mockito.mock(Client.class); -// -// SetOnce actionCompleted = new SetOnce<>(); -// ShrinkAction action = new ShrinkAction(randomIntBetween(1, 10)); -// -// action.execute(index, client, clusterService, new LifecycleAction.Listener() { -// @Override -// public void onSuccess(boolean completed) { -// actionCompleted.set(completed); -// } -// -// @Override -// public void onFailure(Exception e) { -// throw new AssertionError("Unexpected method call", e); -// } -// }); -// -// assertFalse(actionCompleted.get()); -// Mockito.verifyZeroInteractions(client); -// } -// -// public void testExecuteWaitingOnAllShardsActive() { -// Index index = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); -// Index targetIndex = new Index("shrunk-" + index.getName(), randomAlphaOfLengthBetween(1, 20)); -// ClusterService clusterService = Mockito.mock(ClusterService.class); -// IndexMetaData indexMetadata = IndexMetaData.builder(index.getName()) -// .settings(settings(Version.CURRENT)) -// .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); -// IndexMetaData targetIndexMetadata = IndexMetaData.builder(targetIndex.getName()) -// .settings(settings(Version.CURRENT).put(IndexMetaData.INDEX_SHRINK_SOURCE_NAME_KEY, index.getName())) -// .numberOfShards(1).numberOfReplicas(randomIntBetween(0, 5)).build(); -// ImmutableOpenMap.Builder indices = ImmutableOpenMap. builder().fPut(index.getName(), -// indexMetadata).fPut(targetIndex.getName(), targetIndexMetadata); -// ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE).metaData(MetaData.builder().indices(indices.build())) -// .routingTable(RoutingTable.builder() -// .add(IndexRoutingTable.builder(targetIndex).addShard( -// TestShardRouting.newShardRouting(new ShardId(index, 0), "node1", true, -// ShardRoutingState.INITIALIZING))) -// .build()) -// .build(); -// Mockito.when(clusterService.state()).thenReturn(clusterState); -// Client client = Mockito.mock(Client.class); -// SetOnce actionCompleted = new SetOnce<>(); -// ShrinkAction action = new ShrinkAction(randomIntBetween(1, 10)); -// -// action.execute(index, client, clusterService, new LifecycleAction.Listener() { -// @Override -// public void onSuccess(boolean completed) { -// actionCompleted.set(completed); -// } -// -// @Override -// public void onFailure(Exception e) { -// throw new AssertionError("Unexpected method call", e); -// } -// }); -// -// assertFalse(actionCompleted.get()); -// } -// -// public void testExecuteIndexAlreadyExists() { -// Index index = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); -// Index targetIndex = new Index("shrunk-" + index.getName(), randomAlphaOfLengthBetween(1, 20)); -// ClusterService clusterService = Mockito.mock(ClusterService.class); -// IndexMetaData indexMetadata = IndexMetaData.builder(index.getName()) -// .settings(settings(Version.CURRENT)) -// .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); -// IndexMetaData targetIndexMetadata = IndexMetaData.builder(targetIndex.getName()) -// .settings(settings(Version.CURRENT)) -// .numberOfShards(1).numberOfReplicas(randomIntBetween(0, 5)).build(); -// ImmutableOpenMap.Builder indices = ImmutableOpenMap. builder().fPut(index.getName(), -// indexMetadata).fPut(targetIndex.getName(), targetIndexMetadata); -// ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE) -// .metaData(MetaData.builder().indices(indices.build())).build(); -// Mockito.when(clusterService.state()).thenReturn(clusterState); -// Client client = Mockito.mock(Client.class); -// -// SetOnce actionFailed = new SetOnce<>(); -// ShrinkAction action = new ShrinkAction(randomIntBetween(1, 10)); -// -// action.execute(index, client, clusterService, new LifecycleAction.Listener() { -// @Override -// public void onSuccess(boolean completed) { -// throw new AssertionError("Unexpected method call to onSuccess"); -// } -// -// @Override -// public void onFailure(Exception e) { -// actionFailed.set(e); -// } -// }); -// -// assertThat(actionFailed.get().getMessage(), equalTo("Cannot shrink index [" + index.getName() + "]" + -// " because target index [" + targetIndex.getName() + "] already exists.")); -// } + public void testToSteps() { + ShrinkAction action = createTestInstance(); + String phase = randomAlphaOfLengthBetween(1, 10); + StepKey nextStepKey = new StepKey(randomAlphaOfLengthBetween(1, 10), randomAlphaOfLengthBetween(1, 10), + randomAlphaOfLengthBetween(1, 10)); + List steps = action.toSteps(null, phase, nextStepKey); + assertThat(steps.size(), equalTo(4)); + StepKey expectedFirstKey = new StepKey(phase, ShrinkAction.NAME, ShrinkStep.NAME); + StepKey expectedSecondKey = new StepKey(phase, ShrinkAction.NAME, EnoughShardsWaitStep.NAME); + StepKey expectedThirdKey = new StepKey(phase, ShrinkAction.NAME, AliasStep.NAME); + StepKey expectedFourthKey = new StepKey(phase, ShrinkAction.NAME, ShrunkenIndexCheckStep.NAME); + assertTrue(steps.get(0) instanceof ShrinkStep); + assertThat(steps.get(0).getKey(), equalTo(expectedFirstKey)); + assertThat(((ShrinkStep) steps.get(0)).getNumberOfShards(), equalTo(action.getNumberOfShards())); + assertTrue(steps.get(1) instanceof EnoughShardsWaitStep); + assertThat(steps.get(1).getKey(), equalTo(expectedSecondKey)); + assertThat(((EnoughShardsWaitStep) steps.get(1)).getNumberOfShards(), equalTo(action.getNumberOfShards())); + assertTrue(steps.get(2) instanceof AliasStep); + assertThat(steps.get(2).getKey(), equalTo(expectedThirdKey)); + assertTrue(steps.get(3) instanceof ShrunkenIndexCheckStep); + assertThat(steps.get(3).getKey(), equalTo(expectedFourthKey)); + } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/ShrinkStepTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/ShrinkStepTests.java index c1278ecad95..d51af5c1cf6 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/ShrinkStepTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/ShrinkStepTests.java @@ -45,14 +45,14 @@ public class ShrinkStepTests extends ESTestCase { public ShrinkStep createRandomInstance() { StepKey stepKey = new StepKey(randomAlphaOfLength(10), randomAlphaOfLength(10), randomAlphaOfLength(10)); StepKey nextStepKey = new StepKey(randomAlphaOfLength(10), randomAlphaOfLength(10), randomAlphaOfLength(10)); - String shrunkenIndexName = randomAlphaOfLengthBetween(1, 20); - return new ShrinkStep(stepKey, nextStepKey, client, shrunkenIndexName); + int numberOfShards = randomIntBetween(1, 20); + return new ShrinkStep(stepKey, nextStepKey, client, numberOfShards); } public ShrinkStep mutateInstance(ShrinkStep instance) { StepKey key = instance.getKey(); StepKey nextKey = instance.getNextStepKey(); - String shrunkenIndexName = instance.getShrunkenIndexName(); + int numberOfShards = instance.getNumberOfShards(); switch (between(0, 2)) { case 0: @@ -62,32 +62,39 @@ public class ShrinkStepTests extends ESTestCase { nextKey = new StepKey(key.getPhase(), key.getAction(), key.getName() + randomAlphaOfLength(5)); break; case 2: - shrunkenIndexName = shrunkenIndexName + randomAlphaOfLengthBetween(1, 5); + numberOfShards = numberOfShards + 1; break; default: throw new AssertionError("Illegal randomisation branch"); } - return new ShrinkStep(key, nextKey, instance.getClient(), shrunkenIndexName); + return new ShrinkStep(key, nextKey, instance.getClient(), numberOfShards); } public void testHashcodeAndEquals() { EqualsHashCodeTestUtils .checkEqualsAndHashCode(createRandomInstance(), instance -> new ShrinkStep(instance.getKey(), instance.getNextStepKey(), instance.getClient(), - instance.getShrunkenIndexName()), + instance.getNumberOfShards()), this::mutateInstance); } public void testPerformAction() throws Exception { + String lifecycleName = randomAlphaOfLength(5); long creationDate = randomNonNegativeLong(); + ShrinkStep step = createRandomInstance(); IndexMetaData sourceIndexMetaData = IndexMetaData.builder(randomAlphaOfLength(10)) - .settings(settings(Version.CURRENT).put(LifecycleSettings.LIFECYCLE_INDEX_CREATION_DATE, creationDate)) + .settings(settings(Version.CURRENT) + .put(LifecycleSettings.LIFECYCLE_INDEX_CREATION_DATE, creationDate) + .put(LifecycleSettings.LIFECYCLE_NAME, lifecycleName) + .put(LifecycleSettings.LIFECYCLE_PHASE, step.getKey().getPhase()) + .put(LifecycleSettings.LIFECYCLE_ACTION, step.getKey().getAction()) + .put(LifecycleSettings.LIFECYCLE_STEP, ShrunkenIndexCheckStep.NAME) + ) .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)) .putAlias(AliasMetaData.builder("my_alias")) .build(); - ShrinkStep step = createRandomInstance(); AdminClient adminClient = Mockito.mock(AdminClient.class); IndicesAdminClient indicesClient = Mockito.mock(IndicesAdminClient.class); @@ -104,10 +111,16 @@ public class ShrinkStepTests extends ESTestCase { assertThat(request.getSourceIndex(), equalTo(sourceIndexMetaData.getIndex().getName())); assertThat(request.getTargetIndexRequest().aliases(), equalTo(Collections.singleton(new Alias("my_alias")))); assertThat(request.getTargetIndexRequest().settings(), equalTo(Settings.builder() - .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, sourceIndexMetaData.getNumberOfShards()) + .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, step.getNumberOfShards()) .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, sourceIndexMetaData.getNumberOfReplicas()) - .put(LifecycleSettings.LIFECYCLE_INDEX_CREATION_DATE, creationDate).build())); - assertThat(request.getTargetIndexRequest().index(), equalTo(step.getShrunkenIndexName())); + .put(LifecycleSettings.LIFECYCLE_INDEX_CREATION_DATE, creationDate) + .put(LifecycleSettings.LIFECYCLE_NAME, lifecycleName) + .put(LifecycleSettings.LIFECYCLE_PHASE, step.getKey().getPhase()) + .put(LifecycleSettings.LIFECYCLE_ACTION, step.getKey().getAction()) + .put(LifecycleSettings.LIFECYCLE_STEP, ShrunkenIndexCheckStep.NAME) + .build())); + assertThat(request.getTargetIndexRequest().settings() + .getAsInt(IndexMetaData.SETTING_NUMBER_OF_SHARDS, -1), equalTo(step.getNumberOfShards())); ResizeResponse resizeResponse = ResizeAction.INSTANCE.newResponse(); resizeResponse.readFrom(StreamInput.wrap(new byte[] { 1, 1, 1, 1, 1 })); listener.onResponse(resizeResponse); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/ShrunkenIndexCheckStepTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/ShrunkenIndexCheckStepTests.java new file mode 100644 index 00000000000..fe6b5b2c1ba --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/ShrunkenIndexCheckStepTests.java @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.core.indexlifecycle; + +import org.elasticsearch.Version; +import org.elasticsearch.cluster.ClusterName; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.metadata.IndexMetaData; +import org.elasticsearch.cluster.metadata.MetaData; +import org.elasticsearch.test.ESTestCase; + +import static org.hamcrest.Matchers.equalTo; + +public class ShrunkenIndexCheckStepTests extends ESTestCase { + + public void testConditionMet() { + String sourceIndex = randomAlphaOfLengthBetween(1, 10); + IndexMetaData indexMetadata = IndexMetaData.builder(ShrinkStep.SHRUNKEN_INDEX_PREFIX + sourceIndex) + .settings(settings(Version.CURRENT).put(IndexMetaData.INDEX_SHRINK_SOURCE_NAME_KEY, sourceIndex)) + .numberOfShards(1) + .numberOfReplicas(0).build(); + MetaData metaData = MetaData.builder() + .persistentSettings(settings(Version.CURRENT).build()) + .put(IndexMetaData.builder(indexMetadata)) + .build(); + + ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT).metaData(metaData).build(); + ShrunkenIndexCheckStep step = new ShrunkenIndexCheckStep(null, null); + assertTrue(step.isConditionMet(indexMetadata.getIndex(), clusterState)); + } + + public void testConditionNotMetBecauseNotSameShrunkenIndex() { + String sourceIndex = randomAlphaOfLengthBetween(1, 10); + IndexMetaData indexMetadata = IndexMetaData.builder(sourceIndex + "hello") + .settings(settings(Version.CURRENT).put(IndexMetaData.INDEX_SHRINK_SOURCE_NAME_KEY, sourceIndex)) + .numberOfShards(1) + .numberOfReplicas(0).build(); + MetaData metaData = MetaData.builder() + .persistentSettings(settings(Version.CURRENT).build()) + .put(IndexMetaData.builder(indexMetadata)) + .build(); + ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT).metaData(metaData).build(); + ShrunkenIndexCheckStep step = new ShrunkenIndexCheckStep(null, null); + assertFalse(step.isConditionMet(indexMetadata.getIndex(), clusterState)); + } + + public void testIllegalState() { + IndexMetaData indexMetadata = IndexMetaData.builder(randomAlphaOfLength(5)) + .settings(settings(Version.CURRENT)) + .numberOfShards(1) + .numberOfReplicas(0).build(); + MetaData metaData = MetaData.builder() + .persistentSettings(settings(Version.CURRENT).build()) + .put(IndexMetaData.builder(indexMetadata)) + .build(); + ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT).metaData(metaData).build(); + ShrunkenIndexCheckStep step = new ShrunkenIndexCheckStep(null, null); + IllegalStateException exception = expectThrows(IllegalStateException.class, + () -> step.isConditionMet(indexMetadata.getIndex(), clusterState)); + assertThat(exception.getMessage(), + equalTo("step[is-shrunken-index] is checking an un-shrunken index[" + indexMetadata.getIndex().getName() + "]")); + } +}