diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/IndexLifecycleMetadata.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/IndexLifecycleMetadata.java index b7a90a2b6e3..106f29d6e8d 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/IndexLifecycleMetadata.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/IndexLifecycleMetadata.java @@ -33,24 +33,29 @@ import java.util.stream.Collectors; public class IndexLifecycleMetadata implements XPackMetaDataCustom { public static final String TYPE = "index_lifecycle"; + public static final ParseField MAINTENANCE_MODE_FIELD = new ParseField("maintenance_mode"); public static final ParseField POLICIES_FIELD = new ParseField("policies"); - public static final IndexLifecycleMetadata EMPTY = new IndexLifecycleMetadata(Collections.emptySortedMap()); + public static final IndexLifecycleMetadata EMPTY = new IndexLifecycleMetadata(Collections.emptySortedMap(), OperationMode.NORMAL); @SuppressWarnings("unchecked") public static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( TYPE, a -> new IndexLifecycleMetadata( - ObjectParserUtils.convertListToMapValues(LifecyclePolicyMetadata::getName, (List) a[0]))); + ObjectParserUtils.convertListToMapValues(LifecyclePolicyMetadata::getName, (List) a[0]), + OperationMode.valueOf((String) a[1]))); static { PARSER.declareNamedObjects(ConstructingObjectParser.constructorArg(), (p, c, n) -> LifecyclePolicyMetadata.parse(p, n), v -> { throw new IllegalArgumentException("ordered " + POLICIES_FIELD.getPreferredName() + " are not supported"); }, POLICIES_FIELD); + PARSER.declareString(ConstructingObjectParser.constructorArg(), MAINTENANCE_MODE_FIELD); } private final Map policyMetadatas; + private final OperationMode maintenanceMode; - public IndexLifecycleMetadata(Map policies) { + public IndexLifecycleMetadata(Map policies, OperationMode maintenanceMode) { this.policyMetadatas = Collections.unmodifiableMap(policies); + this.maintenanceMode = maintenanceMode; } public IndexLifecycleMetadata(StreamInput in) throws IOException { @@ -60,6 +65,7 @@ public class IndexLifecycleMetadata implements XPackMetaDataCustom { policies.put(in.readString(), new LifecyclePolicyMetadata(in)); } this.policyMetadatas = policies; + this.maintenanceMode = in.readEnum(OperationMode.class); } @Override @@ -69,12 +75,17 @@ public class IndexLifecycleMetadata implements XPackMetaDataCustom { out.writeString(entry.getKey()); entry.getValue().writeTo(out); } + out.writeEnum(maintenanceMode); } public Map getPolicyMetadatas() { return policyMetadatas; } + public OperationMode getMaintenanceMode() { + return maintenanceMode; + } + public Map getPolicies() { return policyMetadatas.values().stream().map(LifecyclePolicyMetadata::getPolicy) .collect(Collectors.toMap(LifecyclePolicy::getName, Function.identity())); @@ -88,6 +99,7 @@ public class IndexLifecycleMetadata implements XPackMetaDataCustom { @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.field(POLICIES_FIELD.getPreferredName(), policyMetadatas); + builder.field(MAINTENANCE_MODE_FIELD.getPreferredName(), maintenanceMode); return builder; } @@ -108,7 +120,7 @@ public class IndexLifecycleMetadata implements XPackMetaDataCustom { @Override public int hashCode() { - return Objects.hash(policyMetadatas); + return Objects.hash(policyMetadatas, maintenanceMode); } @Override @@ -120,7 +132,8 @@ public class IndexLifecycleMetadata implements XPackMetaDataCustom { return false; } IndexLifecycleMetadata other = (IndexLifecycleMetadata) obj; - return Objects.equals(policyMetadatas, other.policyMetadatas); + return Objects.equals(policyMetadatas, other.policyMetadatas) + && Objects.equals(maintenanceMode, other.maintenanceMode); } @Override @@ -131,26 +144,30 @@ public class IndexLifecycleMetadata implements XPackMetaDataCustom { public static class IndexLifecycleMetadataDiff implements NamedDiff { final Diff> policies; + final OperationMode maintenanceMode; IndexLifecycleMetadataDiff(IndexLifecycleMetadata before, IndexLifecycleMetadata after) { this.policies = DiffableUtils.diff(before.policyMetadatas, after.policyMetadatas, DiffableUtils.getStringKeySerializer()); + this.maintenanceMode = after.maintenanceMode; } public IndexLifecycleMetadataDiff(StreamInput in) throws IOException { this.policies = DiffableUtils.readJdkMapDiff(in, DiffableUtils.getStringKeySerializer(), LifecyclePolicyMetadata::new, IndexLifecycleMetadataDiff::readLifecyclePolicyDiffFrom); + this.maintenanceMode = in.readEnum(OperationMode.class); } @Override public MetaData.Custom apply(MetaData.Custom part) { TreeMap newPolicies = new TreeMap<>( policies.apply(((IndexLifecycleMetadata) part).policyMetadatas)); - return new IndexLifecycleMetadata(newPolicies); + return new IndexLifecycleMetadata(newPolicies, this.maintenanceMode); } @Override public void writeTo(StreamOutput out) throws IOException { policies.writeTo(out); + out.writeEnum(maintenanceMode); } @Override diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/LifecycleSettings.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/LifecycleSettings.java index 0eb1149f54d..61660ac6ce8 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/LifecycleSettings.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/LifecycleSettings.java @@ -13,6 +13,7 @@ import org.elasticsearch.common.unit.TimeValue; */ public class LifecycleSettings { public static final String LIFECYCLE_POLL_INTERVAL = "indices.lifecycle.poll_interval"; + public static final String LIFECYCLE_MAINTENANCE_MODE = "indices.lifecycle.maintenance"; public static final String LIFECYCLE_NAME = "index.lifecycle.name"; public static final String LIFECYCLE_PHASE = "index.lifecycle.phase"; public static final String LIFECYCLE_ACTION = "index.lifecycle.action"; diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/OperationMode.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/OperationMode.java new file mode 100644 index 00000000000..1ba5dbcce7d --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/OperationMode.java @@ -0,0 +1,44 @@ +/* + * 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; + +/** + * Enum representing the different modes that Index Lifecycle Service can operate in. + */ +public enum OperationMode { + /** + * This represents a state where no policies are executed + */ + MAINTENANCE { + @Override + public boolean isValidChange(OperationMode nextMode) { + return nextMode == NORMAL; + } + }, + + /** + * this representes a state where only sensitive actions (like {@link ShrinkAction}) will be executed + * until they finish, at which point the operation mode will move to maintenance mode. + */ + MAINTENANCE_REQUESTED { + @Override + public boolean isValidChange(OperationMode nextMode) { + return nextMode == NORMAL || nextMode == MAINTENANCE; + } + }, + + /** + * Normal operation where all policies are executed as normal. + */ + NORMAL { + @Override + public boolean isValidChange(OperationMode nextMode) { + return nextMode == MAINTENANCE_REQUESTED; + } + }; + + public abstract boolean isValidChange(OperationMode nextMode); +} diff --git a/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleRunner.java b/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleRunner.java index 9ec23858433..46bde34e535 100644 --- a/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleRunner.java +++ b/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleRunner.java @@ -57,6 +57,10 @@ public class IndexLifecycleRunner { return; } Step currentStep = getCurrentStep(stepRegistry, policy, indexSettings); + if (currentStep == null) { + throw new IllegalStateException( + "current step for index [" + indexMetaData.getIndex().getName() + "] with policy [" + policy + "] is not recognized"); + } logger.debug("running policy with current-step[" + currentStep.getKey() + "]"); if (currentStep instanceof TerminalPolicyStep) { logger.debug("policy [" + policy + "] for index [" + indexMetaData.getIndex().getName() + "] complete, skipping execution"); @@ -303,7 +307,7 @@ public class IndexLifecycleRunner { return currentState; } } - + private static IndexMetaData.Builder setPolicyForIndex(final String newPolicyName, LifecyclePolicy newPolicy, List failedIndexes, Index index, IndexMetaData indexMetadata) { @@ -321,7 +325,7 @@ public class IndexLifecycleRunner { return null; } } - + private static boolean canSetPolicy(StepKey currentStepKey, String currentPolicyName, LifecyclePolicy newPolicy) { if (Strings.hasLength(currentPolicyName)) { if (ShrinkAction.NAME.equals(currentStepKey.getAction())) { diff --git a/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleService.java b/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleService.java index 1adf8166ea4..cb10a2ff728 100644 --- a/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleService.java +++ b/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleService.java @@ -5,6 +5,7 @@ */ package org.elasticsearch.xpack.indexlifecycle; +import com.carrotsearch.hppc.cursors.ObjectCursor; import org.apache.logging.log4j.Logger; import org.apache.lucene.util.SetOnce; import org.elasticsearch.client.Client; @@ -12,6 +13,7 @@ import org.elasticsearch.cluster.ClusterChangedEvent; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterStateApplier; import org.elasticsearch.cluster.ClusterStateListener; +import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.Strings; import org.elasticsearch.common.component.AbstractComponent; @@ -21,11 +23,15 @@ import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.xpack.core.indexlifecycle.IndexLifecycleMetadata; import org.elasticsearch.xpack.core.indexlifecycle.LifecyclePolicy; import org.elasticsearch.xpack.core.indexlifecycle.LifecycleSettings; +import org.elasticsearch.xpack.core.indexlifecycle.OperationMode; +import org.elasticsearch.xpack.core.indexlifecycle.ShrinkAction; import org.elasticsearch.xpack.core.indexlifecycle.Step.StepKey; import org.elasticsearch.xpack.core.scheduler.SchedulerEngine; import java.io.Closeable; import java.time.Clock; +import java.util.Collections; +import java.util.Set; import java.util.function.LongSupplier; /** @@ -34,6 +40,7 @@ import java.util.function.LongSupplier; public class IndexLifecycleService extends AbstractComponent implements ClusterStateListener, ClusterStateApplier, SchedulerEngine.Listener, Closeable { private static final Logger logger = ESLoggerFactory.getLogger(IndexLifecycleService.class); + private static final Set IGNORE_ACTIONS_MAINTENANCE_REQUESTED = Collections.singleton(ShrinkAction.NAME); private final SetOnce scheduler = new SetOnce<>(); private final Clock clock; @@ -93,7 +100,6 @@ public class IndexLifecycleService extends AbstractComponent boolean pollIntervalSettingChanged = !pollInterval.equals(previousPollInterval); - if (scheduler.get() == null) { // metadata installed and scheduler should be kicked off. start your engines. scheduler.set(new SchedulerEngine(clock)); scheduler.get().register(this); @@ -142,16 +148,51 @@ public class IndexLifecycleService extends AbstractComponent } } + /** + * executes the policy execution on the appropriate indices by running cluster-state tasks per index. + * + * If maintenance-mode was requested, and it is safe to move into maintenance-mode, this will also be done here + * when possible after no policies are executed. + * + * @param clusterState the current cluster state + * @param fromClusterStateChange whether things are triggered from the cluster-state-listener or the scheduler + */ void triggerPolicies(ClusterState clusterState, boolean fromClusterStateChange) { + IndexLifecycleMetadata currentMetadata = clusterState.metaData().custom(IndexLifecycleMetadata.TYPE); + + if (currentMetadata == null) { + return; + } + + OperationMode currentMode = currentMetadata.getMaintenanceMode(); + + if (OperationMode.MAINTENANCE.equals(currentMode)) { + return; + } + + boolean safeToEnterMaintenanceMode = true; // true until proven false by a run policy + // loop through all indices in cluster state and filter for ones that are // managed by the Index Lifecycle Service they have a index.lifecycle.name setting // associated to a policy - clusterState.metaData().indices().valuesIt().forEachRemaining((idxMeta) -> { + for (ObjectCursor cursor : clusterState.metaData().indices().values()) { + IndexMetaData idxMeta = cursor.value; String policyName = LifecycleSettings.LIFECYCLE_NAME_SETTING.get(idxMeta.getSettings()); if (Strings.isNullOrEmpty(policyName) == false) { + StepKey stepKey = IndexLifecycleRunner.getCurrentStepKey(idxMeta.getSettings()); + if (OperationMode.MAINTENANCE_REQUESTED == currentMode && stepKey != null + && IGNORE_ACTIONS_MAINTENANCE_REQUESTED.contains(stepKey.getAction()) == false) { + logger.info("skipping policy [" + policyName + "] for index [" + idxMeta.getIndex().getName() + + "]. maintenance mode requested"); + continue; + } lifecycleRunner.runPolicy(policyName, idxMeta, clusterState, fromClusterStateChange); + safeToEnterMaintenanceMode = false; // proven false! } - }); + } + if (safeToEnterMaintenanceMode && OperationMode.MAINTENANCE_REQUESTED == currentMode) { + submitMaintenanceModeUpdate(OperationMode.MAINTENANCE); + } } @Override @@ -161,4 +202,9 @@ public class IndexLifecycleService extends AbstractComponent engine.stop(); } } + + public void submitMaintenanceModeUpdate(OperationMode mode) { + clusterService.submitStateUpdateTask("ilm_maintenance_update", + new MaintenanceModeUpdateTask(mode)); + } } diff --git a/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/MaintenanceModeUpdateTask.java b/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/MaintenanceModeUpdateTask.java new file mode 100644 index 00000000000..d224a84564b --- /dev/null +++ b/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/MaintenanceModeUpdateTask.java @@ -0,0 +1,49 @@ +/* + * 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.indexlifecycle; + +import org.apache.logging.log4j.Logger; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ClusterStateUpdateTask; +import org.elasticsearch.cluster.metadata.MetaData; +import org.elasticsearch.common.logging.ESLoggerFactory; +import org.elasticsearch.xpack.core.indexlifecycle.IndexLifecycleMetadata; +import org.elasticsearch.xpack.core.indexlifecycle.OperationMode; + +public class MaintenanceModeUpdateTask extends ClusterStateUpdateTask { + private static final Logger logger = ESLoggerFactory.getLogger(MaintenanceModeUpdateTask.class); + private final OperationMode mode; + + public MaintenanceModeUpdateTask(OperationMode mode) { + this.mode = mode; + } + + OperationMode getOperationMode() { + return mode; + } + + @Override + public ClusterState execute(ClusterState currentState) { + IndexLifecycleMetadata currentMetadata = currentState.metaData().custom(IndexLifecycleMetadata.TYPE); + + + if (currentMetadata.getMaintenanceMode().isValidChange(mode) == false) { + return currentState; + } + + ClusterState.Builder builder = new ClusterState.Builder(currentState); + MetaData.Builder metadataBuilder = MetaData.builder(currentState.metaData()); + metadataBuilder.putCustom(IndexLifecycleMetadata.TYPE, + new IndexLifecycleMetadata(currentMetadata.getPolicyMetadatas(), mode)); + builder.metaData(metadataBuilder.build()); + return builder.build(); + } + + @Override + public void onFailure(String source, Exception e) { + logger.error("unable to update lifecycle metadata with new mode [" + mode + "]", e); + } +} diff --git a/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/action/TransportDeleteLifecycleAction.java b/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/action/TransportDeleteLifecycleAction.java index 2defb5ff247..6b38ea77e97 100644 --- a/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/action/TransportDeleteLifecycleAction.java +++ b/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/action/TransportDeleteLifecycleAction.java @@ -81,7 +81,7 @@ public class TransportDeleteLifecycleAction extends TransportMasterNodeAction newPolicies = new TreeMap<>(currentMetadata.getPolicyMetadatas()); newPolicies.remove(request.getPolicyName()); - IndexLifecycleMetadata newMetadata = new IndexLifecycleMetadata(newPolicies); + IndexLifecycleMetadata newMetadata = new IndexLifecycleMetadata(newPolicies, currentMetadata.getMaintenanceMode()); newState.metaData(MetaData.builder(currentState.getMetaData()) .putCustom(IndexLifecycleMetadata.TYPE, newMetadata).build()); return newState.build(); diff --git a/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/action/TransportPutLifecycleAction.java b/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/action/TransportPutLifecycleAction.java index 998252c0713..b9f9814e9c0 100644 --- a/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/action/TransportPutLifecycleAction.java +++ b/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/action/TransportPutLifecycleAction.java @@ -23,6 +23,7 @@ import org.elasticsearch.transport.TransportService; import org.elasticsearch.xpack.core.ClientHelper; import org.elasticsearch.xpack.core.indexlifecycle.IndexLifecycleMetadata; import org.elasticsearch.xpack.core.indexlifecycle.LifecyclePolicyMetadata; +import org.elasticsearch.xpack.core.indexlifecycle.OperationMode; import org.elasticsearch.xpack.core.indexlifecycle.action.PutLifecycleAction; import org.elasticsearch.xpack.core.indexlifecycle.action.PutLifecycleAction.Request; import org.elasticsearch.xpack.core.indexlifecycle.action.PutLifecycleAction.Response; @@ -82,7 +83,7 @@ public class TransportPutLifecycleAction extends TransportMasterNodeAction policies = metadata.getPolicyMetadatas(); policies = new TreeMap<>(policies); - String policyName = randomAlphaOfLength(10); - policies.put(policyName, new LifecyclePolicyMetadata( + OperationMode mode = metadata.getMaintenanceMode(); + if (randomBoolean()) { + String policyName = randomAlphaOfLength(10); + policies.put(policyName, new LifecyclePolicyMetadata( new LifecyclePolicy(TestLifecycleType.INSTANCE, policyName, Collections.emptyMap()), Collections.emptyMap())); - return new IndexLifecycleMetadata(policies); + } else { + mode = randomValueOtherThan(metadata.getMaintenanceMode(), () -> randomFrom(OperationMode.values())); + } + return new IndexLifecycleMetadata(policies, mode); } @Override diff --git a/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleRunnerTests.java b/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleRunnerTests.java index 4221c62ec28..b3aada77976 100644 --- a/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleRunnerTests.java +++ b/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleRunnerTests.java @@ -321,6 +321,22 @@ public class IndexLifecycleRunnerTests extends ESTestCase { Mockito.verifyZeroInteractions(clusterService); } + public void testRunPolicyWithNoStepsInRegistry() { + String policyName = "cluster_state_action_policy"; + StepKey stepKey = new StepKey("phase", "action", "cluster_state_action_step"); + ClusterService clusterService = mock(ClusterService.class); + IndexLifecycleRunner runner = new IndexLifecycleRunner(new PolicyStepsRegistry(), clusterService, () -> 0L); + IndexMetaData indexMetaData = IndexMetaData.builder("my_index").settings(settings(Version.CURRENT)) + .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); + + IllegalStateException exception = expectThrows(IllegalStateException.class, + () -> runner.runPolicy(policyName, indexMetaData, null, randomBoolean())); + assertEquals("current step for index [my_index] with policy [cluster_state_action_policy] is not recognized", + exception.getMessage()); + Mockito.verifyZeroInteractions(clusterService); + + } + public void testRunPolicyUnknownStepType() { String policyName = "cluster_state_action_policy"; StepKey stepKey = new StepKey("phase", "action", "cluster_state_action_step"); diff --git a/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleServiceTests.java b/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleServiceTests.java index e1d4795cefb..3f4cb6222be 100644 --- a/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleServiceTests.java +++ b/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleServiceTests.java @@ -5,6 +5,7 @@ */ package org.elasticsearch.xpack.indexlifecycle; +import org.apache.lucene.util.SetOnce; import org.elasticsearch.Version; import org.elasticsearch.client.AdminClient; import org.elasticsearch.client.Client; @@ -13,28 +14,42 @@ import org.elasticsearch.cluster.ClusterChangedEvent; import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterStateUpdateTask; +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.service.ClusterService; +import org.elasticsearch.common.collect.ImmutableOpenMap; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.index.Index; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.core.indexlifecycle.IndexLifecycleMetadata; +import org.elasticsearch.xpack.core.indexlifecycle.LifecyclePolicy; +import org.elasticsearch.xpack.core.indexlifecycle.LifecyclePolicyMetadata; import org.elasticsearch.xpack.core.indexlifecycle.LifecycleSettings; +import org.elasticsearch.xpack.core.indexlifecycle.MockAction; +import org.elasticsearch.xpack.core.indexlifecycle.OperationMode; +import org.elasticsearch.xpack.core.indexlifecycle.Phase; +import org.elasticsearch.xpack.core.indexlifecycle.ShrinkAction; +import org.elasticsearch.xpack.core.indexlifecycle.Step; +import org.elasticsearch.xpack.core.indexlifecycle.TestLifecycleType; import org.elasticsearch.xpack.core.scheduler.SchedulerEngine; import org.junit.After; import org.junit.Before; import org.mockito.Mockito; -import java.io.IOException; import java.time.Clock; import java.time.Instant; import java.time.ZoneId; +import java.util.Collections; +import java.util.SortedMap; +import java.util.TreeMap; import java.util.concurrent.ExecutorService; import static org.elasticsearch.node.Node.NODE_MASTER_SETTING; +import static org.elasticsearch.xpack.core.indexlifecycle.AbstractStepTestCase.randomStepKey; import static org.hamcrest.Matchers.equalTo; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; @@ -77,12 +92,18 @@ public class IndexLifecycleServiceTests extends ESTestCase { indicesClient = mock(IndicesAdminClient.class); when(client.admin()).thenReturn(adminClient); when(adminClient.indices()).thenReturn(indicesClient); + when(client.settings()).thenReturn(Settings.EMPTY); indexLifecycleService = new IndexLifecycleService(Settings.EMPTY, client, clusterService, clock, () -> now); Mockito.verify(clusterService).addListener(indexLifecycleService); Mockito.verify(clusterService).addStateApplier(indexLifecycleService); } + @After + public void cleanup() { + indexLifecycleService.close(); + } + public void testOnlyChangesStateOnMasterAndMetadataExists() { boolean isMaster = randomBoolean(); String localNodeId = isMaster ? nodeId : nodeId + "not_master"; @@ -176,11 +197,6 @@ public class IndexLifecycleServiceTests extends ESTestCase { assertNotNull(indexLifecycleService.getScheduledJob()); } - @After - public void cleanup() throws IOException { - indexLifecycleService.close(); - } - public void testSchedulerInitializationAndUpdate() { TimeValue pollInterval = TimeValue.timeValueSeconds(randomIntBetween(1, 59)); MetaData metaData = MetaData.builder() @@ -221,222 +237,126 @@ public class IndexLifecycleServiceTests extends ESTestCase { Mockito.verifyNoMoreInteractions(clusterService); } -// /** -// * Checks that a new index does the following successfully: -// * -// * 1. setting index.lifecycle.date -// * 2. sets phase -// * 3. sets action -// * 4. executes action -// */ -// @SuppressWarnings("unchecked") -// public void testTriggeredWithMatchingPolicy() { -// String policyName = randomAlphaOfLengthBetween(1, 20); -// MockAction mockAction = new MockAction(Collections.emptyList()); -// Phase phase = new Phase("phase", TimeValue.ZERO, Collections.singletonMap("action", mockAction)); -// LifecyclePolicy policy = new LifecyclePolicy(TestLifecycleType.INSTANCE, policyName, -// Collections.singletonMap(phase.getName(), phase)); -// SortedMap policyMap = new TreeMap<>(); -// policyMap.put(policyName, policy); -// Index index = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); -// IndexMetaData indexMetadata = IndexMetaData.builder(index.getName()) -// .settings(settings(Version.CURRENT).put(LifecycleSettings.LIFECYCLE_NAME_SETTING.getKey(), policyName)) -// .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); -// ImmutableOpenMap.Builder indices = ImmutableOpenMap. builder() -// .fPut(index.getName(), indexMetadata); -// MetaData metaData = MetaData.builder() -// .putCustom(IndexLifecycleMetadata.TYPE, new IndexLifecycleMetadata(policyMap)) -// .indices(indices.build()) -// .persistentSettings(settings(Version.CURRENT).build()) -// .build(); -// ClusterState currentState = ClusterState.builder(ClusterName.DEFAULT) -// .metaData(metaData) -// .nodes(DiscoveryNodes.builder().localNodeId(nodeId).masterNodeId(nodeId).add(masterNode).build()) -// .build(); -// -// SchedulerEngine.Event schedulerEvent = new SchedulerEngine.Event(IndexLifecycle.NAME, randomLong(), randomLong()); -// -// when(clusterService.state()).thenReturn(currentState); -// -// SetOnce dateUpdated = new SetOnce<>(); -// SetOnce phaseUpdated = new SetOnce<>(); -// SetOnce actionUpdated = new SetOnce<>(); -// doAnswer(invocationOnMock -> { -// UpdateSettingsRequest request = (UpdateSettingsRequest) invocationOnMock.getArguments()[0]; -// ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; -// UpdateSettingsTestHelper.assertSettingsRequest(request, Settings.builder() -// .put(LifecycleSettings.LIFECYCLE_INDEX_CREATION_DATE_SETTING.getKey(), -// indexMetadata.getCreationDate()).build(), index.getName()); -// dateUpdated.set(true); -// listener.onResponse(UpdateSettingsTestHelper.createMockResponse(true)); -// return null; -// }).doAnswer(invocationOnMock -> { -// UpdateSettingsRequest request = (UpdateSettingsRequest) invocationOnMock.getArguments()[0]; -// ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; -// UpdateSettingsTestHelper.assertSettingsRequest(request, Settings.builder() -// .put(LifecycleSettings.LIFECYCLE_ACTION, "") -// .put(LifecycleSettings.LIFECYCLE_ACTION_TIME, -1L) -// .put(LifecycleSettings.LIFECYCLE_PHASE_TIME, now) -// .put(LifecycleSettings.LIFECYCLE_PHASE, "phase").build(), index.getName()); -// phaseUpdated.set(true); -// listener.onResponse(UpdateSettingsTestHelper.createMockResponse(true)); -// return null; -// }).doAnswer(invocationOnMock -> { -// UpdateSettingsRequest request = (UpdateSettingsRequest) invocationOnMock.getArguments()[0]; -// ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; -// UpdateSettingsTestHelper.assertSettingsRequest(request, Settings.builder() -// .put(LifecycleSettings.LIFECYCLE_ACTION, MockAction.NAME) -// .put(LifecycleSettings.LIFECYCLE_ACTION_TIME, now).build(), index.getName()); -// actionUpdated.set(true); -// listener.onResponse(UpdateSettingsTestHelper.createMockResponse(true)); -// return null; -// }).when(indicesClient).updateSettings(any(), any()); -// -// indexLifecycleService.triggered(schedulerEvent); -// -// assertThat(dateUpdated.get(), equalTo(true)); -// assertThat(phaseUpdated.get(), equalTo(true)); -// assertThat(actionUpdated.get(), equalTo(true)); -// } + public void testMaintenanceModeSkip() { + String policyName = randomAlphaOfLengthBetween(1, 20); + IndexLifecycleRunnerTests.MockInitializePolicyContextStep mockStep = + new IndexLifecycleRunnerTests.MockInitializePolicyContextStep(randomStepKey(), randomStepKey()); + MockAction mockAction = new MockAction(Collections.singletonList(mockStep)); + Phase phase = new Phase("phase", TimeValue.ZERO, Collections.singletonMap("action", mockAction)); + LifecyclePolicy policy = new LifecyclePolicy(TestLifecycleType.INSTANCE, policyName, + Collections.singletonMap(phase.getName(), phase)); + SortedMap policyMap = new TreeMap<>(); + policyMap.put(policyName, new LifecyclePolicyMetadata(policy, Collections.emptyMap())); + Index index = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); + IndexMetaData indexMetadata = IndexMetaData.builder(index.getName()) + .settings(settings(Version.CURRENT).put(LifecycleSettings.LIFECYCLE_NAME_SETTING.getKey(), policyName)) + .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); + ImmutableOpenMap.Builder indices = ImmutableOpenMap. builder() + .fPut(index.getName(), indexMetadata); + MetaData metaData = MetaData.builder() + .putCustom(IndexLifecycleMetadata.TYPE, new IndexLifecycleMetadata(policyMap, OperationMode.MAINTENANCE)) + .indices(indices.build()) + .persistentSettings(settings(Version.CURRENT).build()) + .build(); + ClusterState currentState = ClusterState.builder(ClusterName.DEFAULT) + .metaData(metaData) + .nodes(DiscoveryNodes.builder().localNodeId(nodeId).masterNodeId(nodeId).add(masterNode).build()) + .build(); + indexLifecycleService.triggerPolicies(currentState, randomBoolean()); + assertThat(mockStep.getExecuteCount(), equalTo(0L)); + } -// /** -// * Check that a policy is executed without first setting the `index.lifecycle.date` setting -// */ -// @SuppressWarnings("unchecked") -// public void testTriggeredWithDateSettingAlreadyPresent() { -// String policyName = randomAlphaOfLengthBetween(1, 20); -// MockAction mockAction = new MockAction(Collections.emptyList()); -// Phase phase = new Phase("phase", TimeValue.ZERO, Collections.singletonMap("action", mockAction)); -// LifecyclePolicy policy = new LifecyclePolicy(TestLifecycleType.INSTANCE, policyName, -// Collections.singletonMap(phase.getName(), phase)); -// SortedMap policyMap = new TreeMap<>(); -// policyMap.put(policyName, policy); -// Index index = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); -// long creationDate = randomLongBetween(0, now - 1); -// IndexMetaData indexMetadata = IndexMetaData.builder(index.getName()) -// .settings(settings(Version.CURRENT) -// .put(LifecycleSettings.LIFECYCLE_NAME_SETTING.getKey(), policyName) -// .put(LifecycleSettings.LIFECYCLE_INDEX_CREATION_DATE_SETTING.getKey(), creationDate)) -// .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).creationDate(creationDate).build(); -// ImmutableOpenMap.Builder indices = ImmutableOpenMap. builder() -// .fPut(index.getName(), indexMetadata); -// MetaData metaData = MetaData.builder() -// .putCustom(IndexLifecycleMetadata.TYPE, new IndexLifecycleMetadata(policyMap)) -// .indices(indices.build()) -// .persistentSettings(settings(Version.CURRENT).build()) -// .build(); -// ClusterState currentState = ClusterState.builder(ClusterName.DEFAULT) -// .metaData(metaData) -// .nodes(DiscoveryNodes.builder().localNodeId(nodeId).masterNodeId(nodeId).add(masterNode).build()) -// .build(); -// -// SchedulerEngine.Event schedulerEvent = new SchedulerEngine.Event(IndexLifecycle.NAME, randomLong(), randomLong()); -// -// when(clusterService.state()).thenReturn(currentState); -// -// SetOnce dateUpdated = new SetOnce<>(); -// doAnswer(invocationOnMock -> { -// UpdateSettingsRequest request = (UpdateSettingsRequest) invocationOnMock.getArguments()[0]; -// ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; -// try { -// UpdateSettingsTestHelper.assertSettingsRequest(request, Settings.builder() -// .put(LifecycleSettings.LIFECYCLE_INDEX_CREATION_DATE_SETTING.getKey(), -// indexMetadata.getCreationDate()).build(), index.getName()); -// dateUpdated.set(true); -// } catch (AssertionError e) { -// // noop: here because we are either updating the phase or action prior to executing MockAction -// } -// listener.onResponse(UpdateSettingsTestHelper.createMockResponse(true)); -// return null; -// }).when(indicesClient).updateSettings(any(), any()); -// -// indexLifecycleService.triggered(schedulerEvent); -// -// assertNull(dateUpdated.get()); -// } + public void testRequestedMaintenanceOnShrink() { + Step.StepKey mockShrinkStep = new Step.StepKey(randomAlphaOfLength(4), ShrinkAction.NAME, randomAlphaOfLength(5)); + String policyName = randomAlphaOfLengthBetween(1, 20); + IndexLifecycleRunnerTests.MockInitializePolicyContextStep mockStep = + new IndexLifecycleRunnerTests.MockInitializePolicyContextStep(mockShrinkStep, randomStepKey()); + MockAction mockAction = new MockAction(Collections.singletonList(mockStep)); + Phase phase = new Phase("phase", TimeValue.ZERO, Collections.singletonMap("action", mockAction)); + LifecyclePolicy policy = new LifecyclePolicy(TestLifecycleType.INSTANCE, policyName, + Collections.singletonMap(phase.getName(), phase)); + SortedMap policyMap = new TreeMap<>(); + policyMap.put(policyName, new LifecyclePolicyMetadata(policy, Collections.emptyMap())); + Index index = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); + IndexMetaData indexMetadata = IndexMetaData.builder(index.getName()) + .settings(settings(Version.CURRENT).put(LifecycleSettings.LIFECYCLE_NAME_SETTING.getKey(), policyName) + .put(LifecycleSettings.LIFECYCLE_PHASE, mockShrinkStep.getPhase()) + .put(LifecycleSettings.LIFECYCLE_ACTION, mockShrinkStep.getAction()) + .put(LifecycleSettings.LIFECYCLE_STEP, mockShrinkStep.getName())) + .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); + ImmutableOpenMap.Builder indices = ImmutableOpenMap. builder() + .fPut(index.getName(), indexMetadata); + MetaData metaData = MetaData.builder() + .putCustom(IndexLifecycleMetadata.TYPE, new IndexLifecycleMetadata(policyMap, OperationMode.MAINTENANCE_REQUESTED)) + .indices(indices.build()) + .persistentSettings(settings(Version.CURRENT).build()) + .build(); + ClusterState currentState = ClusterState.builder(ClusterName.DEFAULT) + .metaData(metaData) + .nodes(DiscoveryNodes.builder().localNodeId(nodeId).masterNodeId(nodeId).add(masterNode).build()) + .build(); -// /** -// * Check that if an index has an unknown lifecycle policy set it does not -// * execute any policy but does process other indexes. -// */ -// public void testTriggeredUnknownPolicyNameSet() { -// String policyName = randomAlphaOfLengthBetween(1, 20); -// MockAction mockAction = new MockAction(Collections.emptyList()); -// Phase phase = new Phase("phase", TimeValue.ZERO, Collections.singletonMap("action", mockAction)); -// LifecyclePolicy policy = new LifecyclePolicy(TestLifecycleType.INSTANCE, policyName, -// Collections.singletonMap(phase.getName(), phase)); -// SortedMap policyMap = new TreeMap<>(); -// policyMap.put(policyName, policy); -// Index index = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); -// IndexMetaData indexMetadata = IndexMetaData.builder(index.getName()) -// .settings(settings(Version.CURRENT).put(LifecycleSettings.LIFECYCLE_NAME_SETTING.getKey(), "foo")) -// .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); -// Index index2 = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); -// IndexMetaData indexMetadata2 = IndexMetaData.builder(index2.getName()) -// .settings(settings(Version.CURRENT).put(LifecycleSettings.LIFECYCLE_NAME_SETTING.getKey(), policyName)) -// .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); -// ImmutableOpenMap.Builder indices = ImmutableOpenMap. builder().fPut(index.getName(), -// indexMetadata).fPut(index2.getName(), indexMetadata2); -// MetaData metaData = MetaData.builder().putCustom(IndexLifecycleMetadata.TYPE, new IndexLifecycleMetadata(policyMap)) -// .indices(indices.build()).persistentSettings(settings(Version.CURRENT).build()).build(); -// ClusterState currentState = ClusterState.builder(ClusterName.DEFAULT).metaData(metaData) -// .nodes(DiscoveryNodes.builder().localNodeId(nodeId).masterNodeId(nodeId).add(masterNode).build()).build(); -// -// SchedulerEngine.Event schedulerEvent = new SchedulerEngine.Event(IndexLifecycle.NAME, randomLong(), randomLong()); -// -// when(clusterService.state()).thenReturn(currentState); -// -// doAnswer(invocationOnMock -> { -// @SuppressWarnings("unchecked") -// ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; -// listener.onResponse(UpdateSettingsTestHelper.createMockResponse(true)); -// return null; -// -// }).when(indicesClient).updateSettings(any(), any()); -// -// indexLifecycleService.triggered(schedulerEvent); -// } + ClusterChangedEvent event = new ClusterChangedEvent("_source", currentState, ClusterState.EMPTY_STATE); + SetOnce executedShrink = new SetOnce<>(); + doAnswer(invocationOnMock -> { + executedShrink.set(true); + return null; + }).when(clusterService).submitStateUpdateTask(anyString(), any(ExecuteStepsUpdateTask.class)); + indexLifecycleService.applyClusterState(new ClusterChangedEvent("change", currentState, ClusterState.EMPTY_STATE)); + indexLifecycleService.clusterChanged(event); + assertTrue(executedShrink.get()); + } -// /** -// * Check that if an index has no lifecycle policy set it does not execute -// * any policy but does process other indexes. -// */ -// public void testTriggeredNoPolicyNameSet() { -// String policyName = randomAlphaOfLengthBetween(1, 20); -// MockAction mockAction = new MockAction(Collections.emptyList()); -// Phase phase = new Phase("phase", TimeValue.ZERO, Collections.singletonMap("action", mockAction)); -// LifecyclePolicy policy = new LifecyclePolicy(TestLifecycleType.INSTANCE, policyName, -// Collections.singletonMap(phase.getName(), phase)); -// SortedMap policyMap = new TreeMap<>(); -// policyMap.put(policyName, policy); -// Index index = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); -// IndexMetaData indexMetadata = IndexMetaData.builder(index.getName()).settings(settings(Version.CURRENT)) -// .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); -// Index index2 = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); -// IndexMetaData indexMetadata2 = IndexMetaData.builder(index2.getName()) -// .settings(settings(Version.CURRENT).put(LifecycleSettings.LIFECYCLE_NAME_SETTING.getKey(), policyName)) -// .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); -// ImmutableOpenMap.Builder indices = ImmutableOpenMap. builder().fPut(index.getName(), -// indexMetadata).fPut(index2.getName(), indexMetadata2); -// MetaData metaData = MetaData.builder().putCustom(IndexLifecycleMetadata.TYPE, new IndexLifecycleMetadata(policyMap)) -// .indices(indices.build()).persistentSettings(settings(Version.CURRENT).build()).build(); -// ClusterState currentState = ClusterState.builder(ClusterName.DEFAULT).metaData(metaData) -// .nodes(DiscoveryNodes.builder().localNodeId(nodeId).masterNodeId(nodeId).add(masterNode).build()).build(); -// -// SchedulerEngine.Event schedulerEvent = new SchedulerEngine.Event(IndexLifecycle.NAME, randomLong(), randomLong()); -// -// when(clusterService.state()).thenReturn(currentState); -// -// doAnswer(invocationOnMock -> { -// @SuppressWarnings("unchecked") -// ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; -// listener.onResponse(UpdateSettingsTestHelper.createMockResponse(true)); -// return null; -// -// }).when(indicesClient).updateSettings(any(), any()); -// -// indexLifecycleService.triggered(schedulerEvent); -// } + public void testRequestedMaintenanceOnSafeAction() { + String policyName = randomAlphaOfLengthBetween(1, 20); + Step.StepKey currentStepKey = randomStepKey(); + IndexLifecycleRunnerTests.MockInitializePolicyContextStep mockStep = + new IndexLifecycleRunnerTests.MockInitializePolicyContextStep(currentStepKey, randomStepKey()); + MockAction mockAction = new MockAction(Collections.singletonList(mockStep)); + Phase phase = new Phase("phase", TimeValue.ZERO, Collections.singletonMap("action", mockAction)); + LifecyclePolicy policy = new LifecyclePolicy(TestLifecycleType.INSTANCE, policyName, + Collections.singletonMap(phase.getName(), phase)); + SortedMap policyMap = new TreeMap<>(); + policyMap.put(policyName, new LifecyclePolicyMetadata(policy, Collections.emptyMap())); + Index index = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); + IndexMetaData indexMetadata = IndexMetaData.builder(index.getName()) + .settings(settings(Version.CURRENT).put(LifecycleSettings.LIFECYCLE_NAME_SETTING.getKey(), policyName) + .put(LifecycleSettings.LIFECYCLE_PHASE, currentStepKey.getPhase()) + .put(LifecycleSettings.LIFECYCLE_ACTION, currentStepKey.getAction()) + .put(LifecycleSettings.LIFECYCLE_STEP, currentStepKey.getName())) + .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); + ImmutableOpenMap.Builder indices = ImmutableOpenMap. builder() + .fPut(index.getName(), indexMetadata); + MetaData metaData = MetaData.builder() + .putCustom(IndexLifecycleMetadata.TYPE, new IndexLifecycleMetadata(policyMap, OperationMode.MAINTENANCE_REQUESTED)) + .indices(indices.build()) + .persistentSettings(settings(Version.CURRENT).build()) + .build(); + ClusterState currentState = ClusterState.builder(ClusterName.DEFAULT) + .metaData(metaData) + .nodes(DiscoveryNodes.builder().localNodeId(nodeId).masterNodeId(nodeId).add(masterNode).build()) + .build(); + + ClusterChangedEvent event = new ClusterChangedEvent("_source", currentState, ClusterState.EMPTY_STATE); + + SetOnce ranPolicy = new SetOnce<>(); + SetOnce moveToMaintenance = new SetOnce<>(); + doAnswer(invocationOnMock -> { + ranPolicy.set(true); + throw new AssertionError("invalid invocation"); + }).when(clusterService).submitStateUpdateTask(anyString(), any(ExecuteStepsUpdateTask.class)); + + doAnswer(invocationOnMock -> { + MaintenanceModeUpdateTask task = (MaintenanceModeUpdateTask) invocationOnMock.getArguments()[1]; + assertThat(task.getOperationMode(), equalTo(OperationMode.MAINTENANCE)); + moveToMaintenance.set(true); + return null; + }).when(clusterService).submitStateUpdateTask(anyString(), any(MaintenanceModeUpdateTask.class)); + + indexLifecycleService.clusterChanged(event); + assertNull(ranPolicy.get()); + assertTrue(moveToMaintenance.get()); + } public void testTriggeredDifferentJob() { SchedulerEngine.Event schedulerEvent = new SchedulerEngine.Event("foo", randomLong(), randomLong()); diff --git a/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/MaintenanceModeUpdateTaskTests.java b/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/MaintenanceModeUpdateTaskTests.java new file mode 100644 index 00000000000..8534799ea48 --- /dev/null +++ b/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/MaintenanceModeUpdateTaskTests.java @@ -0,0 +1,61 @@ +/* + * 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.indexlifecycle; + +import org.elasticsearch.Version; +import org.elasticsearch.cluster.ClusterName; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.metadata.MetaData; +import org.elasticsearch.common.collect.ImmutableOpenMap; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.core.indexlifecycle.IndexLifecycleMetadata; +import org.elasticsearch.xpack.core.indexlifecycle.OperationMode; + +import java.util.Collections; + +import static org.hamcrest.Matchers.equalTo; + +public class MaintenanceModeUpdateTaskTests extends ESTestCase { + + public void testExecute() { + assertMove(OperationMode.NORMAL, randomFrom(OperationMode.MAINTENANCE_REQUESTED)); + assertMove(OperationMode.MAINTENANCE_REQUESTED, randomFrom(OperationMode.NORMAL, OperationMode.MAINTENANCE)); + assertMove(OperationMode.MAINTENANCE, randomFrom(OperationMode.NORMAL)); + + OperationMode mode = randomFrom(OperationMode.values()); + assertNoMove(mode, mode); + assertNoMove(OperationMode.MAINTENANCE, randomFrom(OperationMode.MAINTENANCE_REQUESTED)); + assertNoMove(OperationMode.NORMAL, randomFrom(OperationMode.MAINTENANCE)); + } + + private void assertMove(OperationMode currentMode, OperationMode requestedMode) { + OperationMode newMode = executeUpdate(currentMode, requestedMode, false); + assertThat(newMode, equalTo(requestedMode)); + } + + private void assertNoMove(OperationMode currentMode, OperationMode requestedMode) { + OperationMode newMode = executeUpdate(currentMode, requestedMode, true); + assertThat(newMode, equalTo(currentMode)); + } + + private OperationMode executeUpdate(OperationMode currentMode, OperationMode requestMode, boolean assertSameClusterState) { + IndexLifecycleMetadata indexLifecycleMetadata = new IndexLifecycleMetadata(Collections.emptyMap(), currentMode); + ImmutableOpenMap.Builder customsMapBuilder = ImmutableOpenMap.builder(); + MetaData metaData = MetaData.builder() + .customs(customsMapBuilder.fPut(IndexLifecycleMetadata.TYPE, indexLifecycleMetadata).build()) + .persistentSettings(settings(Version.CURRENT).build()) + .build(); + ClusterState state = ClusterState.builder(ClusterName.DEFAULT).metaData(metaData).build(); + MaintenanceModeUpdateTask task = new MaintenanceModeUpdateTask(requestMode); + ClusterState newState = task.execute(state); + if (assertSameClusterState) { + assertSame(state, newState); + } + IndexLifecycleMetadata newMetaData = newState.metaData().custom(IndexLifecycleMetadata.TYPE); + assertThat(newMetaData.getPolicyMetadatas(), equalTo(indexLifecycleMetadata.getPolicyMetadatas())); + return newMetaData.getMaintenanceMode(); + } +} diff --git a/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/PolicyStepsRegistryTests.java b/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/PolicyStepsRegistryTests.java index 954c0791fd9..bd9f28c5f0b 100644 --- a/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/PolicyStepsRegistryTests.java +++ b/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/PolicyStepsRegistryTests.java @@ -21,6 +21,7 @@ import org.elasticsearch.xpack.core.indexlifecycle.IndexLifecycleMetadata; import org.elasticsearch.xpack.core.indexlifecycle.LifecyclePolicy; import org.elasticsearch.xpack.core.indexlifecycle.LifecyclePolicyMetadata; import org.elasticsearch.xpack.core.indexlifecycle.LifecyclePolicyTests; +import org.elasticsearch.xpack.core.indexlifecycle.OperationMode; import org.elasticsearch.xpack.core.indexlifecycle.MockStep; import org.elasticsearch.xpack.core.indexlifecycle.Step; import org.mockito.Mockito; @@ -108,7 +109,7 @@ public class PolicyStepsRegistryTests extends ESTestCase { new LifecyclePolicyMetadata(newPolicy, headers)); MetaData metaData = MetaData.builder() .persistentSettings(settings(Version.CURRENT).build()) - .putCustom(IndexLifecycleMetadata.TYPE, new IndexLifecycleMetadata(policyMap)) + .putCustom(IndexLifecycleMetadata.TYPE, new IndexLifecycleMetadata(policyMap, OperationMode.NORMAL)) .build(); String nodeId = randomAlphaOfLength(10); DiscoveryNode masterNode = DiscoveryNode.createLocal(settings(Version.CURRENT) @@ -150,7 +151,8 @@ public class PolicyStepsRegistryTests extends ESTestCase { currentState = ClusterState.builder(currentState) .metaData( MetaData.builder(metaData) - .putCustom(IndexLifecycleMetadata.TYPE, new IndexLifecycleMetadata(Collections.emptyMap()))).build(); + .putCustom(IndexLifecycleMetadata.TYPE, + new IndexLifecycleMetadata(Collections.emptyMap(), OperationMode.NORMAL))).build(); registry.update(currentState, client, () -> 0L); assertTrue(registry.getLifecyclePolicyMap().isEmpty()); assertTrue(registry.getFirstStepMap().isEmpty()); @@ -171,7 +173,7 @@ public class PolicyStepsRegistryTests extends ESTestCase { new LifecyclePolicyMetadata(newPolicy, headers)); MetaData metaData = MetaData.builder() .persistentSettings(settings(Version.CURRENT).build()) - .putCustom(IndexLifecycleMetadata.TYPE, new IndexLifecycleMetadata(policyMap)) + .putCustom(IndexLifecycleMetadata.TYPE, new IndexLifecycleMetadata(policyMap, OperationMode.NORMAL)) .build(); String nodeId = randomAlphaOfLength(10); DiscoveryNode masterNode = DiscoveryNode.createLocal(settings(Version.CURRENT) @@ -192,7 +194,7 @@ public class PolicyStepsRegistryTests extends ESTestCase { MetaData.builder(metaData) .putCustom(IndexLifecycleMetadata.TYPE, new IndexLifecycleMetadata(Collections.singletonMap(policyName, - new LifecyclePolicyMetadata(newPolicy, Collections.emptyMap()))))) + new LifecyclePolicyMetadata(newPolicy, Collections.emptyMap())), OperationMode.NORMAL))) .build(); registry.update(currentState, client, () -> 0L); // TODO(talevy): assert changes... right now we do not support updates to policies. will require internal cleanup