From 7319546db960846161f832a430ce8d5f5f18f48b Mon Sep 17 00:00:00 2001 From: Gordon Brown Date: Fri, 18 Jan 2019 17:03:04 -0700 Subject: [PATCH 1/8] Inject Unfollow before Rollover and Shrink We inject an Unfollow action before Shrink because the Shrink action cannot be safely used on a following index, as it may not be fully caught up with the leader index before the "original" following index is deleted and replaced with a non-following Shrunken index. The Unfollow action will verify that 1) the index is marked as "complete", and 2) all operations up to this point have been replicated from the leader to the follower before explicitly disconnecting the follower from the leader. Injecting an Unfollow action before the Rollover action is done mainly as a convenience: This allow users to use the same lifecycle policy on both the leader and follower cluster without having to explictly modify the policy to unfollow the index, while doing what we expect users to want in most cases. --- .../core/indexlifecycle/ReadOnlyAction.java | 1 - .../TimeseriesLifecycleType.java | 11 +- .../indexlifecycle/CCRIndexLifecycleIT.java | 124 +++++++++++++++++- .../TimeSeriesLifecycleActionsIT.java | 36 +++++ 4 files changed, 160 insertions(+), 12 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/ReadOnlyAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/ReadOnlyAction.java index 0e6486eecb7b0..e338d75a98f82 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/ReadOnlyAction.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/ReadOnlyAction.java @@ -25,7 +25,6 @@ */ public class ReadOnlyAction implements LifecycleAction { public static final String NAME = "readonly"; - public static final ReadOnlyAction INSTANCE = new ReadOnlyAction(); private static final ObjectParser PARSER = new ObjectParser<>(NAME, false, ReadOnlyAction::new); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/TimeseriesLifecycleType.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/TimeseriesLifecycleType.java index 4d1c770cea4bc..a7f8f92e6b829 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/TimeseriesLifecycleType.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/TimeseriesLifecycleType.java @@ -6,14 +6,12 @@ package org.elasticsearch.xpack.core.indexlifecycle; import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.util.set.Sets; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -44,8 +42,6 @@ public class TimeseriesLifecycleType implements LifecycleType { static final Set VALID_WARM_ACTIONS = Sets.newHashSet(ORDERED_VALID_WARM_ACTIONS); static final Set VALID_COLD_ACTIONS = Sets.newHashSet(ORDERED_VALID_COLD_ACTIONS); static final Set VALID_DELETE_ACTIONS = Sets.newHashSet(ORDERED_VALID_DELETE_ACTIONS); - private static final Phase EMPTY_WARM_PHASE = new Phase("warm", TimeValue.ZERO, - Collections.singletonMap("readonly", ReadOnlyAction.INSTANCE)); private static Map> ALLOWED_ACTIONS = new HashMap<>(); static { @@ -72,6 +68,13 @@ public List getOrderedPhases(Map phases) { for (String phaseName : VALID_PHASES) { Phase phase = phases.get(phaseName); if (phase != null) { + Map actions = phase.getActions(); + if (actions.containsKey(UnfollowAction.NAME) == false + && (actions.containsKey(RolloverAction.NAME) || actions.containsKey(ShrinkAction.NAME))) { + Map actionMap = new HashMap<>(phase.getActions()); + actionMap.put(UnfollowAction.NAME, new UnfollowAction()); + phase = new Phase(phase.getName(), phase.getMinimumAge(), actionMap); + } orderedPhases.add(phase); } } diff --git a/x-pack/plugin/ilm/qa/multi-cluster/src/test/java/org/elasticsearch/xpack/indexlifecycle/CCRIndexLifecycleIT.java b/x-pack/plugin/ilm/qa/multi-cluster/src/test/java/org/elasticsearch/xpack/indexlifecycle/CCRIndexLifecycleIT.java index 797916c7c405f..0296b20cf459b 100644 --- a/x-pack/plugin/ilm/qa/multi-cluster/src/test/java/org/elasticsearch/xpack/indexlifecycle/CCRIndexLifecycleIT.java +++ b/x-pack/plugin/ilm/qa/multi-cluster/src/test/java/org/elasticsearch/xpack/indexlifecycle/CCRIndexLifecycleIT.java @@ -33,7 +33,7 @@ public void testBasicCCRAndILMIntegration() throws Exception { String policyName = "basic-test"; if ("leader".equals(targetCluster)) { - putILMPolicy(policyName, "50GB", null, TimeValue.timeValueHours(7*24)); + putILMPolicy(policyName, "50GB", null, TimeValue.timeValueHours(7*24), randomBoolean()); Settings indexSettings = Settings.builder() .put("index.soft_deletes.enabled", true) .put("index.number_of_shards", 1) @@ -45,7 +45,7 @@ public void testBasicCCRAndILMIntegration() throws Exception { ensureGreen(indexName); } else if ("follow".equals(targetCluster)) { // Policy with the same name must exist in follower cluster too: - putILMPolicy(policyName, "50GB", null, TimeValue.timeValueHours(7*24)); + putILMPolicy(policyName, "50GB", null, TimeValue.timeValueHours(7*24), randomBoolean()); followIndex(indexName, indexName); // Aliases are not copied from leader index, so we need to add that for the rollover action in follower cluster: client().performRequest(new Request("PUT", "/" + indexName + "/_alias/logs")); @@ -97,7 +97,7 @@ public void testCcrAndIlmWithRollover() throws Exception { if ("leader".equals(targetCluster)) { // Create a policy on the leader - putILMPolicy(policyName, null, 1, null); + putILMPolicy(policyName, null, 1, null, randomBoolean()); Request templateRequest = new Request("PUT", "_template/my_template"); Settings indexSettings = Settings.builder() .put("index.soft_deletes.enabled", true) @@ -110,7 +110,7 @@ public void testCcrAndIlmWithRollover() throws Exception { assertOK(client().performRequest(templateRequest)); } else if ("follow".equals(targetCluster)) { // Policy with the same name must exist in follower cluster too: - putILMPolicy(policyName, null, 1, null); + putILMPolicy(policyName, null, 1, null, randomBoolean()); // Set up an auto-follow pattern Request createAutoFollowRequest = new Request("PUT", "/_ccr/auto_follow/my_auto_follow_pattern"); @@ -189,7 +189,99 @@ public void testCcrAndIlmWithRollover() throws Exception { } } - private static void putILMPolicy(String name, String maxSize, Integer maxDocs, TimeValue maxAge) throws IOException { + public void testUnfollowInjectedBeforeShrink() throws Exception { + final String indexName = "shrink-test"; + final String shrunkenIndexName = "shrink-" + indexName; + final String policyName = "shrink-test-policy"; + + if ("leader".equals(targetCluster)) { + Settings indexSettings = Settings.builder() + .put("index.soft_deletes.enabled", true) + .put("index.number_of_shards", 3) + .put("index.number_of_replicas", 0) + .put("index.lifecycle.name", policyName) // this policy won't exist on the leader, that's fine + .build(); + createIndex(indexName, indexSettings, "", ""); + ensureGreen(indexName); + } else if ("follow".equals(targetCluster)) { + // Create a policy with just a Shrink action on the follower + final XContentBuilder builder = jsonBuilder(); + builder.startObject(); + { + builder.startObject("policy"); + { + builder.startObject("phases"); + { + builder.startObject("warm"); + { + builder.startObject("actions"); + { + builder.startObject("shrink"); + { + builder.field("number_of_shards", 1); + } + builder.endObject(); + } + builder.endObject(); + } + builder.endObject(); + + // Sometimes throw in an extraneous unfollow just to check it doesn't break anything + if (randomBoolean()) { + builder.startObject("cold"); + { + builder.startObject("actions"); + { + builder.startObject("unfollow"); + builder.endObject(); + } + builder.endObject(); + } + builder.endObject(); + } + } + builder.endObject(); + } + builder.endObject(); + } + builder.endObject(); + + final Request request = new Request("PUT", "_ilm/policy/" + policyName); + request.setJsonEntity(Strings.toString(builder)); + assertOK(client().performRequest(request)); + + // Follow the index + followIndex(indexName, indexName); + // Make sure it actually took + assertBusy(() -> assertTrue(indexExists(indexName))); + // This should now be in the "warm" phase waiting for the index to be ready to unfollow + assertBusy(() -> assertILMPolicy(client(), indexName, policyName, "warm", "unfollow", "wait-for-indexing-complete")); + + // Set the indexing_complete flag on the leader so the index will actually unfollow + try (RestClient leaderClient = buildLeaderClient()) { + updateIndexSettings(leaderClient, indexName, Settings.builder() + .put("index.lifecycle.indexing_complete", true) + .build() + ); + } + + // Wait for the setting to get replicated + assertBusy(() -> assertThat(getIndexSetting(client(), indexName, "index.lifecycle.indexing_complete"), equalTo("true"))); + + // We can't reliably check that the index is unfollowed, because ILM + // moves through the unfollow and shrink actions so fast that the + // index often disappears between assertBusy checks + + // Wait for the index to continue with its lifecycle and be shrunk + assertBusy(() -> assertTrue(indexExists(shrunkenIndexName))); + + // Wait for the index to complete its policy + assertBusy(() -> assertILMPolicy(client(), shrunkenIndexName, policyName, "completed", "completed", "completed")); + } + } + + private static void putILMPolicy(String name, String maxSize, Integer maxDocs, TimeValue maxAge, + boolean explicitUnfollowInHot) throws IOException { final Request request = new Request("PUT", "_ilm/policy/" + name); XContentBuilder builder = jsonBuilder(); builder.startObject(); @@ -214,7 +306,7 @@ private static void putILMPolicy(String name, String maxSize, Integer maxDocs, T } builder.endObject(); } - { + if (explicitUnfollowInHot) { builder.startObject("unfollow"); builder.endObject(); } @@ -225,6 +317,11 @@ private static void putILMPolicy(String name, String maxSize, Integer maxDocs, T { builder.startObject("actions"); { + // Sometimes throw in an extraneous unfollow just to check it doesn't break anything + if (randomBoolean()) { + builder.startObject("unfollow"); + builder.endObject(); + } builder.startObject("readonly"); builder.endObject(); } @@ -253,13 +350,26 @@ private static void putILMPolicy(String name, String maxSize, Integer maxDocs, T } private static void assertILMPolicy(RestClient client, String index, String policy, String expectedPhase) throws IOException { + assertILMPolicy(client, index, policy, expectedPhase, null, null); + } + + private static void assertILMPolicy(RestClient client, String index, String policy, String expectedPhase, + String expectedAction, String expectedStep) throws IOException { final Request request = new Request("GET", "/" + index + "/_ilm/explain"); Map response = toMap(client.performRequest(request)); LOGGER.info("response={}", response); Map explanation = (Map) ((Map) response.get("indices")).get(index); assertThat(explanation.get("managed"), is(true)); assertThat(explanation.get("policy"), equalTo(policy)); - assertThat(explanation.get("phase"), equalTo(expectedPhase)); + if (expectedPhase != null) { + assertThat(explanation.get("phase"), equalTo(expectedPhase)); + } + if (expectedAction != null) { + assertThat(explanation.get("action"), equalTo(expectedAction)); + } + if (expectedStep != null) { + assertThat(explanation.get("step"), equalTo(expectedStep)); + } } private static void updateIndexSettings(RestClient client, String index, Settings settings) throws IOException { diff --git a/x-pack/plugin/ilm/qa/multi-node/src/test/java/org/elasticsearch/xpack/indexlifecycle/TimeSeriesLifecycleActionsIT.java b/x-pack/plugin/ilm/qa/multi-node/src/test/java/org/elasticsearch/xpack/indexlifecycle/TimeSeriesLifecycleActionsIT.java index 01eba362711b3..4e54551f4cb90 100644 --- a/x-pack/plugin/ilm/qa/multi-node/src/test/java/org/elasticsearch/xpack/indexlifecycle/TimeSeriesLifecycleActionsIT.java +++ b/x-pack/plugin/ilm/qa/multi-node/src/test/java/org/elasticsearch/xpack/indexlifecycle/TimeSeriesLifecycleActionsIT.java @@ -626,6 +626,42 @@ public void testRemoveAndReaddPolicy() throws Exception { assertBusy(() -> assertThat(getStepKeyForIndex(originalIndex), equalTo(TerminalPolicyStep.KEY))); } + public void testMoveToInjectedStep() throws Exception { + String shrunkenIndex = ShrinkAction.SHRUNKEN_INDEX_PREFIX + index; + createNewSingletonPolicy("warm", new ShrinkAction(1), TimeValue.timeValueHours(12)); + + createIndexWithSettings(index, Settings.builder().put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 3) + .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0) + .put(LifecycleSettings.LIFECYCLE_NAME, policy) + .put(RolloverAction.LIFECYCLE_ROLLOVER_ALIAS, "alias")); + + assertBusy(() -> assertThat(getStepKeyForIndex(index), equalTo(new StepKey("new", "complete", "complete")))); + + // Move to a step from the injected unfollow action + Request moveToStepRequest = new Request("POST", "_ilm/move/" + index); + moveToStepRequest.setJsonEntity("{\n" + + " \"current_step\": { \n" + + " \"phase\": \"new\",\n" + + " \"action\": \"complete\",\n" + + " \"name\": \"complete\"\n" + + " },\n" + + " \"next_step\": { \n" + + " \"phase\": \"warm\",\n" + + " \"action\": \"unfollow\",\n" + + " \"name\": \"wait-for-indexing-complete\"\n" + + " }\n" + + "}"); + // If we get an OK on this request we have successfully moved to the injected step + assertOK(client().performRequest(moveToStepRequest)); + + // Make sure we actually move on to and execute the shrink action + assertBusy(() -> { + assertTrue(indexExists(shrunkenIndex)); + assertTrue(aliasExists(shrunkenIndex, index)); + assertThat(getStepKeyForIndex(shrunkenIndex), equalTo(TerminalPolicyStep.KEY)); + }); + } + private void createFullPolicy(TimeValue hotTime) throws IOException { Map hotActions = new HashMap<>(); hotActions.put(SetPriorityAction.NAME, new SetPriorityAction(100)); From b4ba59ed30fc33ae439e887a717c5b5a4db7053c Mon Sep 17 00:00:00 2001 From: Gordon Brown Date: Fri, 18 Jan 2019 17:06:52 -0700 Subject: [PATCH 2/8] A bit of clean up --- .../xpack/indexlifecycle/CCRIndexLifecycleIT.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/x-pack/plugin/ilm/qa/multi-cluster/src/test/java/org/elasticsearch/xpack/indexlifecycle/CCRIndexLifecycleIT.java b/x-pack/plugin/ilm/qa/multi-cluster/src/test/java/org/elasticsearch/xpack/indexlifecycle/CCRIndexLifecycleIT.java index 0296b20cf459b..455cb9cb0b018 100644 --- a/x-pack/plugin/ilm/qa/multi-cluster/src/test/java/org/elasticsearch/xpack/indexlifecycle/CCRIndexLifecycleIT.java +++ b/x-pack/plugin/ilm/qa/multi-cluster/src/test/java/org/elasticsearch/xpack/indexlifecycle/CCRIndexLifecycleIT.java @@ -33,7 +33,7 @@ public void testBasicCCRAndILMIntegration() throws Exception { String policyName = "basic-test"; if ("leader".equals(targetCluster)) { - putILMPolicy(policyName, "50GB", null, TimeValue.timeValueHours(7*24), randomBoolean()); + putILMPolicy(policyName, "50GB", null, TimeValue.timeValueHours(7*24)); Settings indexSettings = Settings.builder() .put("index.soft_deletes.enabled", true) .put("index.number_of_shards", 1) @@ -45,7 +45,7 @@ public void testBasicCCRAndILMIntegration() throws Exception { ensureGreen(indexName); } else if ("follow".equals(targetCluster)) { // Policy with the same name must exist in follower cluster too: - putILMPolicy(policyName, "50GB", null, TimeValue.timeValueHours(7*24), randomBoolean()); + putILMPolicy(policyName, "50GB", null, TimeValue.timeValueHours(7*24)); followIndex(indexName, indexName); // Aliases are not copied from leader index, so we need to add that for the rollover action in follower cluster: client().performRequest(new Request("PUT", "/" + indexName + "/_alias/logs")); @@ -97,7 +97,7 @@ public void testCcrAndIlmWithRollover() throws Exception { if ("leader".equals(targetCluster)) { // Create a policy on the leader - putILMPolicy(policyName, null, 1, null, randomBoolean()); + putILMPolicy(policyName, null, 1, null); Request templateRequest = new Request("PUT", "_template/my_template"); Settings indexSettings = Settings.builder() .put("index.soft_deletes.enabled", true) @@ -110,7 +110,7 @@ public void testCcrAndIlmWithRollover() throws Exception { assertOK(client().performRequest(templateRequest)); } else if ("follow".equals(targetCluster)) { // Policy with the same name must exist in follower cluster too: - putILMPolicy(policyName, null, 1, null, randomBoolean()); + putILMPolicy(policyName, null, 1, null); // Set up an auto-follow pattern Request createAutoFollowRequest = new Request("PUT", "/_ccr/auto_follow/my_auto_follow_pattern"); @@ -280,8 +280,7 @@ public void testUnfollowInjectedBeforeShrink() throws Exception { } } - private static void putILMPolicy(String name, String maxSize, Integer maxDocs, TimeValue maxAge, - boolean explicitUnfollowInHot) throws IOException { + private static void putILMPolicy(String name, String maxSize, Integer maxDocs, TimeValue maxAge) throws IOException { final Request request = new Request("PUT", "_ilm/policy/" + name); XContentBuilder builder = jsonBuilder(); builder.startObject(); @@ -306,7 +305,7 @@ private static void putILMPolicy(String name, String maxSize, Integer maxDocs, T } builder.endObject(); } - if (explicitUnfollowInHot) { + if (randomBoolean()) { builder.startObject("unfollow"); builder.endObject(); } From 21a25482e6f3aa2feb5708c1cb583dd9556f478f Mon Sep 17 00:00:00 2001 From: Gordon Brown Date: Tue, 22 Jan 2019 15:20:42 -0700 Subject: [PATCH 3/8] Fix HLRC Explain Lifecycle test --- client/rest-high-level/build.gradle | 1 + .../client/IndexLifecycleIT.java | 52 +++++++++++-------- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/client/rest-high-level/build.gradle b/client/rest-high-level/build.gradle index b71ca82c7d094..22e6252892a7d 100644 --- a/client/rest-high-level/build.gradle +++ b/client/rest-high-level/build.gradle @@ -107,6 +107,7 @@ integTestCluster { // Truststore settings are not used since TLS is not enabled. Included for testing the get certificates API setting 'xpack.security.http.ssl.certificate_authorities', 'testnode.crt' setting 'xpack.security.transport.ssl.truststore.path', 'testnode.jks' + setting 'indices.lifecycle.poll_interval', '1000ms' keystoreSetting 'xpack.security.transport.ssl.truststore.secure_password', 'testnode' setupCommand 'setupDummyUser', 'bin/elasticsearch-users', diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/IndexLifecycleIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/IndexLifecycleIT.java index 4ad6d2e6ce604..f74ba00436c63 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/IndexLifecycleIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/IndexLifecycleIT.java @@ -184,31 +184,37 @@ public void testExplainLifecycle() throws Exception { createIndex("squash", Settings.EMPTY); - ExplainLifecycleRequest req = new ExplainLifecycleRequest("foo-01", "baz-01", "squash"); - ExplainLifecycleResponse response = execute(req, highLevelClient().indexLifecycle()::explainLifecycle, + // The injected Unfollow step will run pretty rapidly here, so we need + // to wait for it to settle into the "stable" step of waiting to be + // ready to roll over + assertBusy(() -> { + ExplainLifecycleRequest req = new ExplainLifecycleRequest("foo-01", "baz-01", "squash"); + ExplainLifecycleResponse response = execute(req, highLevelClient().indexLifecycle()::explainLifecycle, highLevelClient().indexLifecycle()::explainLifecycleAsync); - Map indexResponses = response.getIndexResponses(); - assertEquals(3, indexResponses.size()); - IndexLifecycleExplainResponse fooResponse = indexResponses.get("foo-01"); - assertNotNull(fooResponse); - assertTrue(fooResponse.managedByILM()); - assertEquals("foo-01", fooResponse.getIndex()); - assertEquals("hot", fooResponse.getPhase()); - assertEquals("rollover", fooResponse.getAction()); - assertEquals("check-rollover-ready", fooResponse.getStep()); - assertEquals(new PhaseExecutionInfo(policy.getName(), new Phase("", hotPhase.getMinimumAge(), hotPhase.getActions()), + Map indexResponses = response.getIndexResponses(); + assertEquals(3, indexResponses.size()); + IndexLifecycleExplainResponse fooResponse = indexResponses.get("foo-01"); + assertNotNull(fooResponse); + assertTrue(fooResponse.managedByILM()); + assertEquals("foo-01", fooResponse.getIndex()); + assertEquals("hot", fooResponse.getPhase()); + assertEquals("rollover", fooResponse.getAction()); + assertEquals("check-rollover-ready", fooResponse.getStep()); + assertEquals(new PhaseExecutionInfo(policy.getName(), new Phase("", hotPhase.getMinimumAge(), hotPhase.getActions()), 1L, expectedPolicyModifiedDate), fooResponse.getPhaseExecutionInfo()); - IndexLifecycleExplainResponse bazResponse = indexResponses.get("baz-01"); - assertNotNull(bazResponse); - assertTrue(bazResponse.managedByILM()); - assertEquals("baz-01", bazResponse.getIndex()); - assertEquals("hot", bazResponse.getPhase()); - assertEquals("rollover", bazResponse.getAction()); - assertEquals("check-rollover-ready", bazResponse.getStep()); - IndexLifecycleExplainResponse squashResponse = indexResponses.get("squash"); - assertNotNull(squashResponse); - assertFalse(squashResponse.managedByILM()); - assertEquals("squash", squashResponse.getIndex()); + IndexLifecycleExplainResponse bazResponse = indexResponses.get("baz-01"); + assertNotNull(bazResponse); + assertTrue(bazResponse.managedByILM()); + assertEquals("baz-01", bazResponse.getIndex()); + assertEquals("hot", bazResponse.getPhase()); + assertEquals("rollover", bazResponse.getAction()); + assertEquals("check-rollover-ready", bazResponse.getStep()); + IndexLifecycleExplainResponse squashResponse = indexResponses.get("squash"); + assertNotNull(squashResponse); + assertFalse(squashResponse.managedByILM()); + assertEquals("squash", squashResponse.getIndex()); + + }); } public void testDeleteLifecycle() throws IOException { From da392db9c2c98f0fe6b22b4519c617dc214ad0b4 Mon Sep 17 00:00:00 2001 From: Gordon Brown Date: Tue, 22 Jan 2019 15:53:13 -0700 Subject: [PATCH 4/8] Add some notes about Unfollow injection on actions --- .../reference/ilm/policy-definitions.asciidoc | 19 +++++++++++++++++++ .../ilm/using-policies-rollover.asciidoc | 1 + 2 files changed, 20 insertions(+) diff --git a/docs/reference/ilm/policy-definitions.asciidoc b/docs/reference/ilm/policy-definitions.asciidoc index 881b58826b031..87f60b55189e0 100644 --- a/docs/reference/ilm/policy-definitions.asciidoc +++ b/docs/reference/ilm/policy-definitions.asciidoc @@ -353,6 +353,13 @@ index format must match pattern '^.*-\\d+$', for example (`logs-000001`). The managed index must set `index.lifecycle.rollover_alias` as the alias to rollover. The index must also be the write index for the alias. +[IMPORTANT] +If a policy using the Rollover action is used on a <>, policy execution will wait until the leader index rolls over (or has +<>), then convert the +follower index into a regular index as if <> had been used instead of rolling over. + For example, if an index to be managed has an alias `my_data`. The managed index "my_index" must be the write index for the alias. For more information, read <>. @@ -578,6 +585,13 @@ PUT _ilm/policy/my_policy NOTE: Index will be be made read-only when this action is run (see: <>) +[IMPORTANT] +If a policy using the Shrink action is used on a <>, policy execution will wait until the leader index rolls over (or has +<>), then convert the +follower index into a regular index as if <> had been used before shrink is applied, as shrink cannot be safely +applied to follower indices. This action shrinks an existing index into a new index with fewer primary shards. It calls the <> to shrink the index. @@ -622,6 +636,11 @@ PUT _ilm/policy/my_policy [[ilm-unfollow-action]] ==== Unfollow +[IMPORTANT] +This action may be used explicitly, as shown below, but this action is also run +before <> and <> as described in the documentation for those actions. + This action turns a {ref}/ccr-apis.html[ccr] follower index into a regular index. This can be desired when moving follower indices into the next phase. Also certain actions like shrink diff --git a/docs/reference/ilm/using-policies-rollover.asciidoc b/docs/reference/ilm/using-policies-rollover.asciidoc index 266346fb8629f..dbabbd3333635 100644 --- a/docs/reference/ilm/using-policies-rollover.asciidoc +++ b/docs/reference/ilm/using-policies-rollover.asciidoc @@ -123,6 +123,7 @@ When the rollover is performed, the newly-created index is set as the write index for the rolled over alias. Documents sent to the alias are indexed into the new index, enabling indexing to continue uninterrupted. +[[skipping-rollover]] === Skipping Rollover The `index.lifecycle.indexing_complete` setting indicates to {ilm} whether this From fe6d06f155bb072e6e4eab8314c12beb173e9338 Mon Sep 17 00:00:00 2001 From: Gordon Brown Date: Tue, 22 Jan 2019 16:10:54 -0700 Subject: [PATCH 5/8] Add note about conditions to Unfollow docs --- docs/reference/ilm/policy-definitions.asciidoc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/reference/ilm/policy-definitions.asciidoc b/docs/reference/ilm/policy-definitions.asciidoc index 87f60b55189e0..5846001b49496 100644 --- a/docs/reference/ilm/policy-definitions.asciidoc +++ b/docs/reference/ilm/policy-definitions.asciidoc @@ -646,6 +646,17 @@ into a regular index. This can be desired when moving follower indices into the next phase. Also certain actions like shrink and rollover can then be performed safely on follower indices. +This action will wait until is it safe to convert a follower index into a +regular index. In particular, the following conditions must be met: + +* The leader index must have `index.lifecycle.indexing_complete` set to `true`. +This happens automatically if the leader index is rolled over using +<>, or may be set manually using +theIndex Settings API. +* All operations performed on the leader index must have been replicated to the +follower index. This ensures that no operations will be lost when the index is +converted into a regular index. + If the unfollow action encounters a follower index then the following operations will be performed on it: From 41f5ed9222d30db4360585795363f84adbcf253b Mon Sep 17 00:00:00 2001 From: Gordon Brown Date: Tue, 22 Jan 2019 16:45:45 -0700 Subject: [PATCH 6/8] Add unit test for Unfollow injection --- .../TimeseriesLifecycleTypeTests.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/TimeseriesLifecycleTypeTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/TimeseriesLifecycleTypeTests.java index 4efb34873d471..ca1614e0bbe62 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/TimeseriesLifecycleTypeTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/TimeseriesLifecycleTypeTests.java @@ -152,6 +152,32 @@ public void testGetOrderedPhases() { assertTrue(isSorted(TimeseriesLifecycleType.INSTANCE.getOrderedPhases(phaseMap), Phase::getName, VALID_PHASES)); } + public void testUnfollowInjections() { + assertTrue(isUnfollowInjected("hot", RolloverAction.NAME)); + assertTrue(isUnfollowInjected("warm", ShrinkAction.NAME)); + + assertFalse(isUnfollowInjected("hot", SetPriorityAction.NAME)); + assertFalse(isUnfollowInjected("warm", SetPriorityAction.NAME)); + assertFalse(isUnfollowInjected("warm", AllocateAction.NAME)); + assertFalse(isUnfollowInjected("warm", ReadOnlyAction.NAME)); + assertFalse(isUnfollowInjected("warm", ForceMergeAction.NAME)); + assertFalse(isUnfollowInjected("cold", SetPriorityAction.NAME)); + assertFalse(isUnfollowInjected("cold", AllocateAction.NAME)); + assertFalse(isUnfollowInjected("cold", FreezeAction.NAME)); + assertFalse(isUnfollowInjected("delete", DeleteAction.NAME)); + + } + + private boolean isUnfollowInjected(String phaseName, String actionName) { + Map phaseMap = new HashMap<>(); + Map actionsMap = new HashMap<>(); + actionsMap.put(actionName, getTestAction(actionName)); + Phase warmPhase = new Phase(phaseName, TimeValue.ZERO, actionsMap); + phaseMap.put(phaseName, warmPhase); + List phases = TimeseriesLifecycleType.INSTANCE.getOrderedPhases(phaseMap); + Phase processedWarmPhase = phases.stream().filter(phase -> phase.getName().equals(phaseName)).findFirst().get(); + return processedWarmPhase.getActions().containsKey("unfollow"); + } public void testGetOrderedActionsInvalidPhase() { IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> TimeseriesLifecycleType.INSTANCE From 7a426388d76709b52ee003761c9aa58b80c04d6a Mon Sep 17 00:00:00 2001 From: Gordon Brown Date: Tue, 22 Jan 2019 17:34:20 -0700 Subject: [PATCH 7/8] Fix documentation test --- .../documentation/ILMDocumentationIT.java | 105 ++++++++++-------- 1 file changed, 56 insertions(+), 49 deletions(-) diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ILMDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ILMDocumentationIT.java index bde59194a2e79..9e0663cdad546 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ILMDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ILMDocumentationIT.java @@ -22,6 +22,7 @@ import org.apache.http.util.EntityUtils; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.LatchedActionListener; +import org.elasticsearch.action.admin.indices.alias.Alias; import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; import org.elasticsearch.client.ESRestHighLevelClientTestCase; import org.elasticsearch.client.RequestOptions; @@ -39,17 +40,17 @@ import org.elasticsearch.client.indexlifecycle.LifecycleManagementStatusRequest; import org.elasticsearch.client.indexlifecycle.LifecycleManagementStatusResponse; import org.elasticsearch.client.indexlifecycle.LifecyclePolicy; -import org.elasticsearch.client.indexlifecycle.OperationMode; import org.elasticsearch.client.indexlifecycle.LifecyclePolicyMetadata; +import org.elasticsearch.client.indexlifecycle.OperationMode; import org.elasticsearch.client.indexlifecycle.Phase; import org.elasticsearch.client.indexlifecycle.PutLifecyclePolicyRequest; import org.elasticsearch.client.indexlifecycle.RemoveIndexLifecyclePolicyRequest; import org.elasticsearch.client.indexlifecycle.RemoveIndexLifecyclePolicyResponse; import org.elasticsearch.client.indexlifecycle.RetryLifecyclePolicyRequest; import org.elasticsearch.client.indexlifecycle.RolloverAction; +import org.elasticsearch.client.indexlifecycle.ShrinkAction; import org.elasticsearch.client.indexlifecycle.StartILMRequest; import org.elasticsearch.client.indexlifecycle.StopILMRequest; -import org.elasticsearch.client.indexlifecycle.ShrinkAction; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.common.Strings; import org.elasticsearch.common.collect.ImmutableOpenMap; @@ -337,11 +338,13 @@ public void testExplainLifecycle() throws Exception { new PutLifecyclePolicyRequest(policy); client.indexLifecycle().putLifecyclePolicy(putRequest, RequestOptions.DEFAULT); - CreateIndexRequest createIndexRequest = new CreateIndexRequest("my_index", + CreateIndexRequest createIndexRequest = new CreateIndexRequest("my_index-1", Settings.builder() .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) .put("index.lifecycle.name", "my_policy") + .put("index.lifecycle.rollover_alias", "my_alias") .build()); + createIndexRequest.alias(new Alias("my_alias").writeIndex(true)); client.indices().create(createIndexRequest, RequestOptions.DEFAULT); CreateIndexRequest createOtherIndexRequest = new CreateIndexRequest("other_index", Settings.builder() @@ -352,58 +355,62 @@ public void testExplainLifecycle() throws Exception { // wait for the policy to become active assertBusy(() -> assertNotNull(client.indexLifecycle() - .explainLifecycle(new ExplainLifecycleRequest("my_index"), RequestOptions.DEFAULT) - .getIndexResponses().get("my_index").getAction())); + .explainLifecycle(new ExplainLifecycleRequest("my_index-1"), RequestOptions.DEFAULT) + .getIndexResponses().get("my_index-1").getAction())); } // tag::ilm-explain-lifecycle-request ExplainLifecycleRequest request = - new ExplainLifecycleRequest("my_index", "other_index"); // <1> + new ExplainLifecycleRequest("my_index-1", "other_index"); // <1> // end::ilm-explain-lifecycle-request - // tag::ilm-explain-lifecycle-execute - ExplainLifecycleResponse response = client.indexLifecycle() - .explainLifecycle(request, RequestOptions.DEFAULT); - // end::ilm-explain-lifecycle-execute - assertNotNull(response); - - // tag::ilm-explain-lifecycle-response - Map indices = - response.getIndexResponses(); - IndexLifecycleExplainResponse myIndex = indices.get("my_index"); - String policyName = myIndex.getPolicyName(); // <1> - boolean isManaged = myIndex.managedByILM(); // <2> - - String phase = myIndex.getPhase(); // <3> - long phaseTime = myIndex.getPhaseTime(); // <4> - String action = myIndex.getAction(); // <5> - long actionTime = myIndex.getActionTime(); - String step = myIndex.getStep(); // <6> - long stepTime = myIndex.getStepTime(); - - String failedStep = myIndex.getFailedStep(); // <7> - // end::ilm-explain-lifecycle-response - assertEquals("my_policy", policyName); - assertTrue(isManaged); - - assertEquals("hot", phase); - assertNotEquals(0, phaseTime); - assertEquals("rollover", action); - assertNotEquals(0, actionTime); - assertEquals("check-rollover-ready", step); - assertNotEquals(0, stepTime); - - assertNull(failedStep); - - IndexLifecycleExplainResponse otherIndex = indices.get("other_index"); - assertFalse(otherIndex.managedByILM()); - assertNull(otherIndex.getPolicyName()); - assertNull(otherIndex.getPhase()); - assertNull(otherIndex.getAction()); - assertNull(otherIndex.getStep()); - assertNull(otherIndex.getFailedStep()); - assertNull(otherIndex.getPhaseExecutionInfo()); - assertNull(otherIndex.getStepInfo()); + + assertBusy(() -> { + // tag::ilm-explain-lifecycle-execute + ExplainLifecycleResponse response = client.indexLifecycle() + .explainLifecycle(request, RequestOptions.DEFAULT); + // end::ilm-explain-lifecycle-execute + assertNotNull(response); + + // tag::ilm-explain-lifecycle-response + Map indices = + response.getIndexResponses(); + IndexLifecycleExplainResponse myIndex = indices.get("my_index-1"); + String policyName = myIndex.getPolicyName(); // <1> + boolean isManaged = myIndex.managedByILM(); // <2> + + String phase = myIndex.getPhase(); // <3> + long phaseTime = myIndex.getPhaseTime(); // <4> + String action = myIndex.getAction(); // <5> + long actionTime = myIndex.getActionTime(); + String step = myIndex.getStep(); // <6> + long stepTime = myIndex.getStepTime(); + + String failedStep = myIndex.getFailedStep(); // <7> + // end::ilm-explain-lifecycle-response + + assertEquals("my_policy", policyName); + assertTrue(isManaged); + + assertEquals("hot", phase); + assertNotEquals(0, phaseTime); + assertEquals("rollover", action); + assertNotEquals(0, actionTime); + assertEquals("check-rollover-ready", step); + assertNotEquals(0, stepTime); + + assertNull(failedStep); + + IndexLifecycleExplainResponse otherIndex = indices.get("other_index"); + assertFalse(otherIndex.managedByILM()); + assertNull(otherIndex.getPolicyName()); + assertNull(otherIndex.getPhase()); + assertNull(otherIndex.getAction()); + assertNull(otherIndex.getStep()); + assertNull(otherIndex.getFailedStep()); + assertNull(otherIndex.getPhaseExecutionInfo()); + assertNull(otherIndex.getStepInfo()); + }); // tag::ilm-explain-lifecycle-execute-listener ActionListener listener = From 1ad537cd83c01c113ef5d38bc397b9c0db7ea0c9 Mon Sep 17 00:00:00 2001 From: Gordon Brown Date: Wed, 23 Jan 2019 11:10:58 -0700 Subject: [PATCH 8/8] Fix typo and add link to Settings API --- docs/reference/ilm/policy-definitions.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/ilm/policy-definitions.asciidoc b/docs/reference/ilm/policy-definitions.asciidoc index 5846001b49496..e16b414504a64 100644 --- a/docs/reference/ilm/policy-definitions.asciidoc +++ b/docs/reference/ilm/policy-definitions.asciidoc @@ -652,7 +652,7 @@ regular index. In particular, the following conditions must be met: * The leader index must have `index.lifecycle.indexing_complete` set to `true`. This happens automatically if the leader index is rolled over using <>, or may be set manually using -theIndex Settings API. +the <>. * All operations performed on the leader index must have been replicated to the follower index. This ensures that no operations will be lost when the index is converted into a regular index.