Browse Source

Add max_single_primary_size as a condition for the ILM rollover action (#68917)

Joe Gallo 4 years ago
parent
commit
d24f5cbc91
21 changed files with 249 additions and 120 deletions
  1. 26 9
      client/rest-high-level/src/main/java/org/elasticsearch/client/ilm/RolloverAction.java
  2. 1 1
      client/rest-high-level/src/test/java/org/elasticsearch/client/IndexLifecycleIT.java
  3. 4 4
      client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ILMDocumentationIT.java
  4. 11 7
      client/rest-high-level/src/test/java/org/elasticsearch/client/ilm/RolloverActionTests.java
  5. 10 0
      docs/reference/ilm/actions/ilm-rollover.asciidoc
  6. 41 18
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/RolloverAction.java
  7. 16 6
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/WaitForRolloverReadyStep.java
  8. 18 6
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/RolloverActionTests.java
  9. 7 3
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/TimeseriesLifecycleTypeTests.java
  10. 36 15
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/WaitForRolloverReadyStepTests.java
  11. 1 1
      x-pack/plugin/ilm/qa/multi-node/src/javaRestTest/java/org/elasticsearch/xpack/TimeSeriesRestDriver.java
  12. 2 2
      x-pack/plugin/ilm/qa/multi-node/src/javaRestTest/java/org/elasticsearch/xpack/ilm/ChangePolicyforIndexIT.java
  13. 2 2
      x-pack/plugin/ilm/qa/multi-node/src/javaRestTest/java/org/elasticsearch/xpack/ilm/TimeSeriesDataStreamsIT.java
  14. 55 27
      x-pack/plugin/ilm/qa/multi-node/src/javaRestTest/java/org/elasticsearch/xpack/ilm/TimeSeriesLifecycleActionsIT.java
  15. 2 2
      x-pack/plugin/ilm/qa/multi-node/src/javaRestTest/java/org/elasticsearch/xpack/ilm/TimeseriesMoveToStepIT.java
  16. 1 1
      x-pack/plugin/ilm/qa/multi-node/src/javaRestTest/java/org/elasticsearch/xpack/ilm/actions/ReadonlyActionIT.java
  17. 5 5
      x-pack/plugin/ilm/qa/multi-node/src/javaRestTest/java/org/elasticsearch/xpack/ilm/actions/SearchableSnapshotActionIT.java
  18. 1 1
      x-pack/plugin/ilm/qa/multi-node/src/javaRestTest/java/org/elasticsearch/xpack/ilm/actions/ShrinkActionIT.java
  19. 1 1
      x-pack/plugin/ilm/qa/with-security/src/javaRestTest/java/org/elasticsearch/xpack/security/PermissionsIT.java
  20. 1 1
      x-pack/plugin/ilm/src/internalClusterTest/java/org/elasticsearch/xpack/ilm/ILMMultiNodeIT.java
  21. 8 8
      x-pack/plugin/ilm/src/test/java/org/elasticsearch/xpack/ilm/action/TransportPutLifecycleActionTests.java

+ 26 - 9
client/rest-high-level/src/main/java/org/elasticsearch/client/ilm/RolloverAction.java

@@ -20,43 +20,56 @@ import org.elasticsearch.common.xcontent.XContentParser;
 import java.io.IOException;
 import java.util.Objects;
 
-
 public class RolloverAction implements LifecycleAction, ToXContentObject {
     public static final String NAME = "rollover";
     private static final ParseField MAX_SIZE_FIELD = new ParseField("max_size");
-    private static final ParseField MAX_DOCS_FIELD = new ParseField("max_docs");
+    private static final ParseField MAX_SINGLE_PRIMARY_SIZE_FIELD = new ParseField("max_single_primary_size");
     private static final ParseField MAX_AGE_FIELD = new ParseField("max_age");
+    private static final ParseField MAX_DOCS_FIELD = new ParseField("max_docs");
 
     private static final ConstructingObjectParser<RolloverAction, Void> PARSER = new ConstructingObjectParser<>(NAME, true,
-        a -> new RolloverAction((ByteSizeValue) a[0], (TimeValue) a[1], (Long) a[2]));
+        a -> new RolloverAction((ByteSizeValue) a[0], (ByteSizeValue) a[1], (TimeValue) a[2], (Long) a[3]));
+
     static {
         PARSER.declareField(ConstructingObjectParser.optionalConstructorArg(),
-            (p, c) -> ByteSizeValue.parseBytesSizeValue(p.text(), MAX_SIZE_FIELD.getPreferredName()), MAX_SIZE_FIELD, ValueType.VALUE);
+            (p, c) -> ByteSizeValue.parseBytesSizeValue(p.text(), MAX_SIZE_FIELD.getPreferredName()),
+            MAX_SIZE_FIELD, ValueType.VALUE);
+        PARSER.declareField(ConstructingObjectParser.optionalConstructorArg(),
+            (p, c) -> ByteSizeValue.parseBytesSizeValue(p.text(), MAX_SINGLE_PRIMARY_SIZE_FIELD.getPreferredName()),
+            MAX_SINGLE_PRIMARY_SIZE_FIELD, ValueType.VALUE);
         PARSER.declareField(ConstructingObjectParser.optionalConstructorArg(),
-            (p, c) -> TimeValue.parseTimeValue(p.text(), MAX_AGE_FIELD.getPreferredName()), MAX_AGE_FIELD, ValueType.VALUE);
+            (p, c) -> TimeValue.parseTimeValue(p.text(), MAX_AGE_FIELD.getPreferredName()),
+            MAX_AGE_FIELD, ValueType.VALUE);
         PARSER.declareLong(ConstructingObjectParser.optionalConstructorArg(), MAX_DOCS_FIELD);
     }
 
     private final ByteSizeValue maxSize;
-    private final Long maxDocs;
+    private final ByteSizeValue maxSinglePrimarySize;
     private final TimeValue maxAge;
+    private final Long maxDocs;
 
     public static RolloverAction parse(XContentParser parser) {
         return PARSER.apply(parser, null);
     }
 
-    public RolloverAction(ByteSizeValue maxSize, TimeValue maxAge, Long maxDocs) {
-        if (maxSize == null && maxAge == null && maxDocs == null) {
+    public RolloverAction(ByteSizeValue maxSize, ByteSizeValue maxSinglePrimarySize, TimeValue maxAge, Long maxDocs) {
+        if (maxSize == null && maxSinglePrimarySize == null && maxAge == null && maxDocs == null) {
             throw new IllegalArgumentException("At least one rollover condition must be set.");
         }
         this.maxSize = maxSize;
+        this.maxSinglePrimarySize = maxSinglePrimarySize;
         this.maxAge = maxAge;
         this.maxDocs = maxDocs;
     }
+
     public ByteSizeValue getMaxSize() {
         return maxSize;
     }
 
+    public ByteSizeValue getMaxSinglePrimarySize() {
+        return maxSinglePrimarySize;
+    }
+
     public TimeValue getMaxAge() {
         return maxAge;
     }
@@ -76,6 +89,9 @@ public class RolloverAction implements LifecycleAction, ToXContentObject {
         if (maxSize != null) {
             builder.field(MAX_SIZE_FIELD.getPreferredName(), maxSize.getStringRep());
         }
+        if (maxSinglePrimarySize != null) {
+            builder.field(MAX_SINGLE_PRIMARY_SIZE_FIELD.getPreferredName(), maxSinglePrimarySize.getStringRep());
+        }
         if (maxAge != null) {
             builder.field(MAX_AGE_FIELD.getPreferredName(), maxAge.getStringRep());
         }
@@ -88,7 +104,7 @@ public class RolloverAction implements LifecycleAction, ToXContentObject {
 
     @Override
     public int hashCode() {
-        return Objects.hash(maxSize, maxAge, maxDocs);
+        return Objects.hash(maxSize, maxSinglePrimarySize, maxAge, maxDocs);
     }
 
     @Override
@@ -101,6 +117,7 @@ public class RolloverAction implements LifecycleAction, ToXContentObject {
         }
         RolloverAction other = (RolloverAction) obj;
         return Objects.equals(maxSize, other.maxSize) &&
+            Objects.equals(maxSinglePrimarySize, other.maxSinglePrimarySize) &&
             Objects.equals(maxAge, other.maxAge) &&
             Objects.equals(maxDocs, other.maxDocs);
     }

+ 1 - 1
client/rest-high-level/src/test/java/org/elasticsearch/client/IndexLifecycleIT.java

@@ -138,7 +138,7 @@ public class IndexLifecycleIT extends ESRestHighLevelClientTestCase {
     public void testExplainLifecycle() throws Exception {
         Map<String, Phase> lifecyclePhases = new HashMap<>();
         Map<String, LifecycleAction> hotActions = new HashMap<>();
-        hotActions.put(RolloverAction.NAME, new RolloverAction(null, TimeValue.timeValueHours(50 * 24), null));
+        hotActions.put(RolloverAction.NAME, new RolloverAction(null, null, TimeValue.timeValueHours(50 * 24), null));
         Phase hotPhase = new Phase("hot", randomFrom(TimeValue.ZERO, null), hotActions);
         lifecyclePhases.put("hot", hotPhase);
 

+ 4 - 4
client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ILMDocumentationIT.java

@@ -92,7 +92,7 @@ public class ILMDocumentationIT extends ESRestHighLevelClientTestCase {
         Map<String, Phase> phases = new HashMap<>();
         Map<String, LifecycleAction> hotActions = new HashMap<>();
         hotActions.put(RolloverAction.NAME, new RolloverAction(
-                new ByteSizeValue(50, ByteSizeUnit.GB), null, null));
+                new ByteSizeValue(50, ByteSizeUnit.GB), null, null, null));
         phases.put("hot", new Phase("hot", TimeValue.ZERO, hotActions)); // <1>
 
         Map<String, LifecycleAction> deleteActions =
@@ -164,7 +164,7 @@ public class ILMDocumentationIT extends ESRestHighLevelClientTestCase {
             Map<String, Phase> phases = new HashMap<>();
             Map<String, LifecycleAction> hotActions = new HashMap<>();
             hotActions.put(RolloverAction.NAME, new RolloverAction(
-                new ByteSizeValue(50, ByteSizeUnit.GB), null, null));
+                new ByteSizeValue(50, ByteSizeUnit.GB), null, null, null));
             phases.put("hot", new Phase("hot", TimeValue.ZERO, hotActions));
             Map<String, LifecycleAction> deleteActions =
                 Collections.singletonMap(DeleteAction.NAME,
@@ -239,7 +239,7 @@ public class ILMDocumentationIT extends ESRestHighLevelClientTestCase {
             Map<String, Phase> phases = new HashMap<>();
             Map<String, LifecycleAction> hotActions = new HashMap<>();
             hotActions.put(RolloverAction.NAME, new RolloverAction(
-                new ByteSizeValue(50, ByteSizeUnit.GB), null, null));
+                new ByteSizeValue(50, ByteSizeUnit.GB), null, null, null));
             phases.put("hot", new Phase("hot", TimeValue.ZERO, hotActions));
 
             Map<String, LifecycleAction> deleteActions =
@@ -339,7 +339,7 @@ public class ILMDocumentationIT extends ESRestHighLevelClientTestCase {
             Map<String, Phase> phases = new HashMap<>();
             Map<String, LifecycleAction> hotActions = new HashMap<>();
             hotActions.put(RolloverAction.NAME, new RolloverAction(
-                new ByteSizeValue(50, ByteSizeUnit.GB), null, null));
+                new ByteSizeValue(50, ByteSizeUnit.GB), null, null, null));
             phases.put("hot", new Phase("hot", TimeValue.ZERO, hotActions));
 
             LifecyclePolicy policy = new LifecyclePolicy("my_policy",

+ 11 - 7
client/rest-high-level/src/test/java/org/elasticsearch/client/ilm/RolloverActionTests.java

@@ -32,17 +32,21 @@ public class RolloverActionTests extends AbstractXContentTestCase<RolloverAction
 
     static RolloverAction randomInstance() {
         ByteSizeUnit maxSizeUnit = randomFrom(ByteSizeUnit.values());
-        ByteSizeValue maxSize = randomBoolean() ? null : new ByteSizeValue(randomNonNegativeLong() / maxSizeUnit.toBytes(1), maxSizeUnit);
-        Long maxDocs = randomBoolean() ? null : randomNonNegativeLong();
-        TimeValue maxAge = (maxDocs == null && maxSize == null || randomBoolean())
-            ? TimeValue.parseTimeValue(randomPositiveTimeValue(), "rollover_action_test")
-            : null;
-        return new RolloverAction(maxSize, maxAge, maxDocs);
+        ByteSizeValue maxSize = randomBoolean()
+            ? null : new ByteSizeValue(randomNonNegativeLong() / maxSizeUnit.toBytes(1), maxSizeUnit);
+        ByteSizeUnit maxSinglePrimarySizeUnit = randomFrom(ByteSizeUnit.values());
+        ByteSizeValue maxSinglePrimarySize = randomBoolean()
+            ? null : new ByteSizeValue(randomNonNegativeLong() / maxSinglePrimarySizeUnit.toBytes(1), maxSinglePrimarySizeUnit);
+        TimeValue maxAge = randomBoolean()
+            ? null : TimeValue.parseTimeValue(randomPositiveTimeValue(), "rollover_action_test");
+        Long maxDocs = (maxSize == null && maxSinglePrimarySize == null && maxAge == null || randomBoolean())
+            ? randomNonNegativeLong() : null;
+        return new RolloverAction(maxSize, maxSinglePrimarySize, maxAge, maxDocs);
     }
 
     public void testNoConditions() {
         IllegalArgumentException exception = expectThrows(IllegalArgumentException.class,
-            () -> new RolloverAction(null, null, null));
+            () -> new RolloverAction(null, null, null, null));
         assertEquals("At least one rollover condition must be set.", exception.getMessage());
     }
 }

+ 10 - 0
docs/reference/ilm/actions/ilm-rollover.asciidoc

@@ -71,6 +71,16 @@ Replicas are not counted toward the maximum index size.
 TIP: To see the current index size, use the <<cat-indices, _cat indices>> API. 
 The `pri.store.size` value shows the combined size of all primary shards.
 
+`max_single_primary_size`::
+(Optional, <<byte-units, byte units>>)
+Triggers roll over when the largest primary shard in the index reaches a certain size.
+This is the maximum size of the primary shards in the index. As with `max_size`,
+replicas are ignored.
++
+TIP: To see the current shard size, use the <<cat-shards, _cat shards>> API.
+The `store` value shows the size each shard, and `prirep` indicates whether a
+shard is a primary (`p`) or a replica (`r`).
+
 [[ilm-rollover-ex]]
 ==== Example
 

+ 41 - 18
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/RolloverAction.java

@@ -6,6 +6,7 @@
  */
 package org.elasticsearch.xpack.core.ilm;
 
+import org.elasticsearch.Version;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.common.Nullable;
 import org.elasticsearch.common.ParseField;
@@ -34,6 +35,7 @@ public class RolloverAction implements LifecycleAction {
     public static final String NAME = "rollover";
     public static final String INDEXING_COMPLETE_STEP_NAME = "set-indexing-complete";
     public static final ParseField MAX_SIZE_FIELD = new ParseField("max_size");
+    public static final ParseField MAX_SINGLE_PRIMARY_SIZE_FIELD = new ParseField("max_single_primary_size");
     public static final ParseField MAX_DOCS_FIELD = new ParseField("max_docs");
     public static final ParseField MAX_AGE_FIELD = new ParseField("max_age");
     public static final String LIFECYCLE_ROLLOVER_ALIAS = "index.lifecycle.rollover_alias";
@@ -41,16 +43,23 @@ public class RolloverAction implements LifecycleAction {
         Setting.Property.Dynamic, Setting.Property.IndexScope);
 
     private static final ConstructingObjectParser<RolloverAction, Void> PARSER = new ConstructingObjectParser<>(NAME,
-            a -> new RolloverAction((ByteSizeValue) a[0], (TimeValue) a[1], (Long) a[2]));
+        a -> new RolloverAction((ByteSizeValue) a[0], (ByteSizeValue) a[1], (TimeValue) a[2], (Long) a[3]));
+
     static {
         PARSER.declareField(ConstructingObjectParser.optionalConstructorArg(),
-                (p, c) -> ByteSizeValue.parseBytesSizeValue(p.text(), MAX_SIZE_FIELD.getPreferredName()), MAX_SIZE_FIELD, ValueType.VALUE);
+            (p, c) -> ByteSizeValue.parseBytesSizeValue(p.text(), MAX_SIZE_FIELD.getPreferredName()),
+            MAX_SIZE_FIELD, ValueType.VALUE);
+        PARSER.declareField(ConstructingObjectParser.optionalConstructorArg(),
+            (p, c) -> ByteSizeValue.parseBytesSizeValue(p.text(), MAX_SINGLE_PRIMARY_SIZE_FIELD.getPreferredName()),
+            MAX_SINGLE_PRIMARY_SIZE_FIELD, ValueType.VALUE);
         PARSER.declareField(ConstructingObjectParser.optionalConstructorArg(),
-                (p, c) -> TimeValue.parseTimeValue(p.text(), MAX_AGE_FIELD.getPreferredName()), MAX_AGE_FIELD, ValueType.VALUE);
+            (p, c) -> TimeValue.parseTimeValue(p.text(), MAX_AGE_FIELD.getPreferredName()),
+            MAX_AGE_FIELD, ValueType.VALUE);
         PARSER.declareLong(ConstructingObjectParser.optionalConstructorArg(), MAX_DOCS_FIELD);
     }
 
     private final ByteSizeValue maxSize;
+    private final ByteSizeValue maxSinglePrimarySize;
     private final Long maxDocs;
     private final TimeValue maxAge;
 
@@ -58,11 +67,13 @@ public class RolloverAction implements LifecycleAction {
         return PARSER.apply(parser, null);
     }
 
-    public RolloverAction(@Nullable ByteSizeValue maxSize, @Nullable TimeValue maxAge, @Nullable Long maxDocs) {
-        if (maxSize == null && maxAge == null && maxDocs == null) {
+    public RolloverAction(@Nullable ByteSizeValue maxSize, @Nullable ByteSizeValue maxSinglePrimarySize, @Nullable TimeValue maxAge,
+                          @Nullable Long maxDocs) {
+        if (maxSize == null && maxSinglePrimarySize == null && maxAge == null && maxDocs == null) {
             throw new IllegalArgumentException("At least one rollover condition must be set.");
         }
         this.maxSize = maxSize;
+        this.maxSinglePrimarySize = maxSinglePrimarySize;
         this.maxAge = maxAge;
         this.maxDocs = maxDocs;
     }
@@ -73,12 +84,13 @@ public class RolloverAction implements LifecycleAction {
         } else {
             maxSize = null;
         }
-        maxAge = in.readOptionalTimeValue();
-        if (in.readBoolean()) {
-            maxDocs = in.readVLong();
+        if (in.getVersion().onOrAfter(Version.V_8_0_0) && in.readBoolean()) {
+            maxSinglePrimarySize = new ByteSizeValue(in);
         } else {
-            maxDocs = null;
+            maxSinglePrimarySize = null;
         }
+        maxAge = in.readOptionalTimeValue();
+        maxDocs = in.readOptionalVLong();
     }
 
     @Override
@@ -88,12 +100,15 @@ public class RolloverAction implements LifecycleAction {
         if (hasMaxSize) {
             maxSize.writeTo(out);
         }
-        out.writeOptionalTimeValue(maxAge);
-        boolean hasMaxDocs = maxDocs != null;
-        out.writeBoolean(hasMaxDocs);
-        if (hasMaxDocs) {
-            out.writeVLong(maxDocs);
+        if (out.getVersion().onOrAfter((Version.V_8_0_0))) {
+            boolean hasMaxSinglePrimarySize = maxSinglePrimarySize != null;
+            out.writeBoolean(hasMaxSinglePrimarySize);
+            if (hasMaxSinglePrimarySize) {
+                maxSinglePrimarySize.writeTo(out);
+            }
         }
+        out.writeOptionalTimeValue(maxAge);
+        out.writeOptionalVLong(maxDocs);
     }
 
     @Override
@@ -105,6 +120,10 @@ public class RolloverAction implements LifecycleAction {
         return maxSize;
     }
 
+    public ByteSizeValue getMaxSinglePrimarySize() {
+        return maxSinglePrimarySize;
+    }
+
     public TimeValue getMaxAge() {
         return maxAge;
     }
@@ -119,6 +138,9 @@ public class RolloverAction implements LifecycleAction {
         if (maxSize != null) {
             builder.field(MAX_SIZE_FIELD.getPreferredName(), maxSize.getStringRep());
         }
+        if (maxSinglePrimarySize != null) {
+            builder.field(MAX_SINGLE_PRIMARY_SIZE_FIELD.getPreferredName(), maxSinglePrimarySize.getStringRep());
+        }
         if (maxAge != null) {
             builder.field(MAX_AGE_FIELD.getPreferredName(), maxAge.getStringRep());
         }
@@ -145,7 +167,7 @@ public class RolloverAction implements LifecycleAction {
         StepKey setIndexingCompleteStepKey = new StepKey(phase, NAME, INDEXING_COMPLETE_STEP_NAME);
 
         WaitForRolloverReadyStep waitForRolloverReadyStep = new WaitForRolloverReadyStep(waitForRolloverReadyStepKey, rolloverStepKey,
-            client, maxSize, maxAge, maxDocs);
+            client, maxSize, maxSinglePrimarySize, maxAge, maxDocs);
         RolloverStep rolloverStep = new RolloverStep(rolloverStepKey, waitForActiveShardsKey, client);
         WaitForActiveShardsStep waitForActiveShardsStep = new WaitForActiveShardsStep(waitForActiveShardsKey, updateDateStepKey);
         UpdateRolloverLifecycleDateStep updateDateStep = new UpdateRolloverLifecycleDateStep(updateDateStepKey, setIndexingCompleteStepKey,
@@ -157,7 +179,7 @@ public class RolloverAction implements LifecycleAction {
 
     @Override
     public int hashCode() {
-        return Objects.hash(maxSize, maxAge, maxDocs);
+        return Objects.hash(maxSize, maxSinglePrimarySize, maxAge, maxDocs);
     }
 
     @Override
@@ -170,8 +192,9 @@ public class RolloverAction implements LifecycleAction {
         }
         RolloverAction other = (RolloverAction) obj;
         return Objects.equals(maxSize, other.maxSize) &&
-                Objects.equals(maxAge, other.maxAge) &&
-                Objects.equals(maxDocs, other.maxDocs);
+            Objects.equals(maxSinglePrimarySize, other.maxSinglePrimarySize) &&
+            Objects.equals(maxAge, other.maxAge) &&
+            Objects.equals(maxDocs, other.maxDocs);
     }
 
     @Override

+ 16 - 6
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/WaitForRolloverReadyStep.java

@@ -35,13 +35,15 @@ public class WaitForRolloverReadyStep extends AsyncWaitStep {
     public static final String NAME = "check-rollover-ready";
 
     private final ByteSizeValue maxSize;
+    private final ByteSizeValue maxSinglePrimarySize;
     private final TimeValue maxAge;
     private final Long maxDocs;
 
-    public WaitForRolloverReadyStep(StepKey key, StepKey nextStepKey, Client client, ByteSizeValue maxSize, TimeValue maxAge,
-                                    Long maxDocs) {
+    public WaitForRolloverReadyStep(StepKey key, StepKey nextStepKey, Client client,
+                                    ByteSizeValue maxSize, ByteSizeValue maxSinglePrimarySize, TimeValue maxAge, Long maxDocs) {
         super(key, nextStepKey, client);
         this.maxSize = maxSize;
+        this.maxSinglePrimarySize = maxSinglePrimarySize;
         this.maxAge = maxAge;
         this.maxDocs = maxDocs;
     }
@@ -139,12 +141,15 @@ public class WaitForRolloverReadyStep extends AsyncWaitStep {
 
         RolloverRequest rolloverRequest = new RolloverRequest(rolloverTarget, null).masterNodeTimeout(masterTimeout);
         rolloverRequest.dryRun(true);
-        if (maxAge != null) {
-            rolloverRequest.addMaxIndexAgeCondition(maxAge);
-        }
         if (maxSize != null) {
             rolloverRequest.addMaxIndexSizeCondition(maxSize);
         }
+        if (maxSinglePrimarySize != null) {
+            rolloverRequest.addMaxSinglePrimarySizeCondition(maxSinglePrimarySize);
+        }
+        if (maxAge != null) {
+            rolloverRequest.addMaxIndexAgeCondition(maxAge);
+        }
         if (maxDocs != null) {
             rolloverRequest.addMaxIndexDocsCondition(maxDocs);
         }
@@ -157,6 +162,10 @@ public class WaitForRolloverReadyStep extends AsyncWaitStep {
         return maxSize;
     }
 
+    ByteSizeValue getMaxSinglePrimarySize() {
+        return maxSinglePrimarySize;
+    }
+
     TimeValue getMaxAge() {
         return maxAge;
     }
@@ -167,7 +176,7 @@ public class WaitForRolloverReadyStep extends AsyncWaitStep {
 
     @Override
     public int hashCode() {
-        return Objects.hash(super.hashCode(), maxSize, maxAge, maxDocs);
+        return Objects.hash(super.hashCode(), maxSize, maxSinglePrimarySize, maxAge, maxDocs);
     }
 
     @Override
@@ -181,6 +190,7 @@ public class WaitForRolloverReadyStep extends AsyncWaitStep {
         WaitForRolloverReadyStep other = (WaitForRolloverReadyStep) obj;
         return super.equals(obj) &&
             Objects.equals(maxSize, other.maxSize) &&
+            Objects.equals(maxSinglePrimarySize, other.maxSinglePrimarySize) &&
             Objects.equals(maxAge, other.maxAge) &&
             Objects.equals(maxDocs, other.maxDocs);
     }

+ 18 - 6
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/RolloverActionTests.java

@@ -30,12 +30,16 @@ public class RolloverActionTests extends AbstractActionTestCase<RolloverAction>
 
     public static RolloverAction randomInstance() {
         ByteSizeUnit maxSizeUnit = randomFrom(ByteSizeUnit.values());
-        ByteSizeValue maxSize = randomBoolean() ? null : new ByteSizeValue(randomNonNegativeLong() / maxSizeUnit.toBytes(1), maxSizeUnit);
+        ByteSizeValue maxSize = randomBoolean() ? null :
+            new ByteSizeValue(randomNonNegativeLong() / maxSizeUnit.toBytes(1), maxSizeUnit);
+        ByteSizeUnit maxSinglePrimarySizeUnit = randomFrom(ByteSizeUnit.values());
+        ByteSizeValue maxSinglePrimarySize = randomBoolean() ? null :
+            new ByteSizeValue(randomNonNegativeLong() / maxSinglePrimarySizeUnit.toBytes(1), maxSinglePrimarySizeUnit);
         Long maxDocs = randomBoolean() ? null : randomNonNegativeLong();
         TimeValue maxAge = (maxDocs == null && maxSize == null || randomBoolean())
             ? TimeValue.parseTimeValue(randomPositiveTimeValue(), "rollover_action_test")
             : null;
-        return new RolloverAction(maxSize, maxAge, maxDocs);
+        return new RolloverAction(maxSize, maxSinglePrimarySize, maxAge, maxDocs);
     }
 
     @Override
@@ -46,9 +50,10 @@ public class RolloverActionTests extends AbstractActionTestCase<RolloverAction>
     @Override
     protected RolloverAction mutateInstance(RolloverAction instance) throws IOException {
         ByteSizeValue maxSize = instance.getMaxSize();
+        ByteSizeValue maxSinglePrimarySize = instance.getMaxSinglePrimarySize();
         TimeValue maxAge = instance.getMaxAge();
         Long maxDocs = instance.getMaxDocs();
-        switch (between(0, 2)) {
+        switch (between(0, 3)) {
             case 0:
                 maxSize = randomValueOtherThan(maxSize, () -> {
                     ByteSizeUnit maxSizeUnit = randomFrom(ByteSizeUnit.values());
@@ -56,21 +61,27 @@ public class RolloverActionTests extends AbstractActionTestCase<RolloverAction>
                 });
                 break;
             case 1:
+                maxSinglePrimarySize = randomValueOtherThan(maxSinglePrimarySize, () -> {
+                    ByteSizeUnit maxSinglePrimarySizeUnit = randomFrom(ByteSizeUnit.values());
+                    return new ByteSizeValue(randomNonNegativeLong() / maxSinglePrimarySizeUnit.toBytes(1), maxSinglePrimarySizeUnit);
+                });
+                break;
+            case 2:
                 maxAge = randomValueOtherThan(maxAge,
                     () -> TimeValue.parseTimeValue(randomPositiveTimeValue(), "rollover_action_test"));
                 break;
-            case 2:
+            case 3:
                 maxDocs = maxDocs == null ? randomNonNegativeLong() : maxDocs + 1;
                 break;
             default:
                 throw new AssertionError("Illegal randomisation branch");
         }
-        return new RolloverAction(maxSize, maxAge, maxDocs);
+        return new RolloverAction(maxSize, maxSinglePrimarySize, maxAge, maxDocs);
     }
 
     public void testNoConditions() {
         IllegalArgumentException exception = expectThrows(IllegalArgumentException.class,
-                () -> new RolloverAction(null, null, null));
+                () -> new RolloverAction(null, null, null, null));
         assertEquals("At least one rollover condition must be set.", exception.getMessage());
     }
 
@@ -102,6 +113,7 @@ public class RolloverActionTests extends AbstractActionTestCase<RolloverAction>
         assertEquals(fourthStep.getKey(), thirdStep.getNextStepKey());
         assertEquals(fifthStep.getKey(), fourthStep.getNextStepKey());
         assertEquals(action.getMaxSize(), firstStep.getMaxSize());
+        assertEquals(action.getMaxSinglePrimarySize(), firstStep.getMaxSinglePrimarySize());
         assertEquals(action.getMaxAge(), firstStep.getMaxAge());
         assertEquals(action.getMaxDocs(), firstStep.getMaxDocs());
         assertEquals(nextStepKey, fifthStep.getNextStepKey());

+ 7 - 3
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/TimeseriesLifecycleTypeTests.java

@@ -54,7 +54,7 @@ public class TimeseriesLifecycleTypeTests extends ESTestCase {
     private static final DeleteAction TEST_DELETE_ACTION = new DeleteAction();
     private static final WaitForSnapshotAction TEST_WAIT_FOR_SNAPSHOT_ACTION = new WaitForSnapshotAction("policy");
     private static final ForceMergeAction TEST_FORCE_MERGE_ACTION = new ForceMergeAction(1, null);
-    private static final RolloverAction TEST_ROLLOVER_ACTION = new RolloverAction(new ByteSizeValue(1), null, null);
+    private static final RolloverAction TEST_ROLLOVER_ACTION = new RolloverAction(new ByteSizeValue(1), null, null, null);
     private static final ShrinkAction TEST_SHRINK_ACTION = new ShrinkAction(1, null);
     private static final ReadOnlyAction TEST_READ_ONLY_ACTION = new ReadOnlyAction();
     private static final FreezeAction TEST_FREEZE_ACTION = new FreezeAction();
@@ -254,7 +254,7 @@ public class TimeseriesLifecycleTypeTests extends ESTestCase {
         }
 
         {
-            Phase hot = new Phase("hot", TimeValue.ZERO, Map.of(RolloverAction.NAME, new RolloverAction(null, null, 1L),
+            Phase hot = new Phase("hot", TimeValue.ZERO, Map.of(RolloverAction.NAME, new RolloverAction(null, null, null, 1L),
             SearchableSnapshotAction.NAME, new SearchableSnapshotAction(randomAlphaOfLengthBetween(4, 10))));
             Phase warm = new Phase("warm", TimeValue.ZERO, Map.of(ForceMergeAction.NAME, new ForceMergeAction(1, null)));
             Phase cold = new Phase("cold", TimeValue.ZERO, Map.of(FreezeAction.NAME, new FreezeAction()));
@@ -730,7 +730,11 @@ public class TimeseriesLifecycleTypeTests extends ESTestCase {
             case ReadOnlyAction.NAME:
                 return new ReadOnlyAction();
             case RolloverAction.NAME:
-                return new RolloverAction(ByteSizeValue.parseBytesSizeValue("0b", "test"), TimeValue.ZERO, 1L);
+                return new RolloverAction(
+                    ByteSizeValue.parseBytesSizeValue("0b", "test"),
+                    ByteSizeValue.parseBytesSizeValue("0b", "test"),
+                    TimeValue.ZERO,
+                    1L);
             case ShrinkAction.NAME:
                 return new ShrinkAction(1, null);
             case FreezeAction.NAME:

+ 36 - 15
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/WaitForRolloverReadyStepTests.java

@@ -13,6 +13,7 @@ import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.action.admin.indices.rollover.Condition;
 import org.elasticsearch.action.admin.indices.rollover.MaxAgeCondition;
 import org.elasticsearch.action.admin.indices.rollover.MaxDocsCondition;
+import org.elasticsearch.action.admin.indices.rollover.MaxSinglePrimarySizeCondition;
 import org.elasticsearch.action.admin.indices.rollover.MaxSizeCondition;
 import org.elasticsearch.action.admin.indices.rollover.RolloverInfo;
 import org.elasticsearch.action.admin.indices.rollover.RolloverRequest;
@@ -49,12 +50,16 @@ public class WaitForRolloverReadyStepTests extends AbstractStepTestCase<WaitForR
         Step.StepKey stepKey = randomStepKey();
         Step.StepKey nextStepKey = randomStepKey();
         ByteSizeUnit maxSizeUnit = randomFrom(ByteSizeUnit.values());
-        ByteSizeValue maxSize = randomBoolean() ? null : new ByteSizeValue(randomNonNegativeLong() / maxSizeUnit.toBytes(1), maxSizeUnit);
+        ByteSizeValue maxSize = randomBoolean() ? null :
+            new ByteSizeValue(randomNonNegativeLong() / maxSizeUnit.toBytes(1), maxSizeUnit);
+        ByteSizeUnit maxSinglePrimarySizeUnit = randomFrom(ByteSizeUnit.values());
+        ByteSizeValue maxSinglePrimarySize = randomBoolean() ? null :
+            new ByteSizeValue(randomNonNegativeLong() / maxSinglePrimarySizeUnit.toBytes(1), maxSinglePrimarySizeUnit);
         Long maxDocs = randomBoolean() ? null : randomNonNegativeLong();
         TimeValue maxAge = (maxDocs == null && maxSize == null || randomBoolean())
             ? TimeValue.parseTimeValue(randomPositiveTimeValue(), "rollover_action_test")
             : null;
-        return new WaitForRolloverReadyStep(stepKey, nextStepKey, client, maxSize, maxAge, maxDocs);
+        return new WaitForRolloverReadyStep(stepKey, nextStepKey, client, maxSize, maxSinglePrimarySize, maxAge, maxDocs);
     }
 
     @Override
@@ -62,10 +67,11 @@ public class WaitForRolloverReadyStepTests extends AbstractStepTestCase<WaitForR
         Step.StepKey key = instance.getKey();
         Step.StepKey nextKey = instance.getNextStepKey();
         ByteSizeValue maxSize = instance.getMaxSize();
+        ByteSizeValue maxSinglePrimarySize = instance.getMaxSinglePrimarySize();
         TimeValue maxAge = instance.getMaxAge();
         Long maxDocs = instance.getMaxDocs();
 
-        switch (between(0, 4)) {
+        switch (between(0, 5)) {
             case 0:
                 key = new Step.StepKey(key.getPhase(), key.getAction(), key.getName() + randomAlphaOfLength(5));
                 break;
@@ -79,21 +85,27 @@ public class WaitForRolloverReadyStepTests extends AbstractStepTestCase<WaitForR
                 });
                 break;
             case 3:
-                maxAge = randomValueOtherThan(maxAge, () -> TimeValue.parseTimeValue(randomPositiveTimeValue(), "rollover_action_test"));
+                maxSinglePrimarySize = randomValueOtherThan(maxSinglePrimarySize, () -> {
+                    ByteSizeUnit maxSinglePrimarySizeUnit = randomFrom(ByteSizeUnit.values());
+                    return new ByteSizeValue(randomNonNegativeLong() / maxSinglePrimarySizeUnit.toBytes(1), maxSinglePrimarySizeUnit);
+                });
                 break;
             case 4:
+                maxAge = randomValueOtherThan(maxAge, () -> TimeValue.parseTimeValue(randomPositiveTimeValue(), "rollover_action_test"));
+                break;
+            case 5:
                 maxDocs = randomValueOtherThan(maxDocs, () -> randomNonNegativeLong());
                 break;
             default:
                 throw new AssertionError("Illegal randomisation branch");
         }
-        return new WaitForRolloverReadyStep(key, nextKey, instance.getClient(), maxSize, maxAge, maxDocs);
+        return new WaitForRolloverReadyStep(key, nextKey, instance.getClient(), maxSize, maxSinglePrimarySize, maxAge, maxDocs);
     }
 
     @Override
     protected WaitForRolloverReadyStep copyInstance(WaitForRolloverReadyStep instance) {
         return new WaitForRolloverReadyStep(instance.getKey(), instance.getNextStepKey(), instance.getClient(),
-            instance.getMaxSize(), instance.getMaxAge(), instance.getMaxDocs());
+            instance.getMaxSize(), instance.getMaxSinglePrimarySize(), instance.getMaxAge(), instance.getMaxDocs());
     }
 
     private static void assertRolloverIndexRequest(RolloverRequest request, String rolloverTarget, Set<Condition<?>> expectedConditions) {
@@ -219,12 +231,15 @@ public class WaitForRolloverReadyStepTests extends AbstractStepTestCase<WaitForR
             @SuppressWarnings("unchecked")
             ActionListener<RolloverResponse> listener = (ActionListener<RolloverResponse>) invocation.getArguments()[1];
             Set<Condition<?>> expectedConditions = new HashSet<>();
-            if (step.getMaxAge() != null) {
-                expectedConditions.add(new MaxAgeCondition(step.getMaxAge()));
-            }
             if (step.getMaxSize() != null) {
                 expectedConditions.add(new MaxSizeCondition(step.getMaxSize()));
             }
+            if (step.getMaxSinglePrimarySize() != null) {
+                expectedConditions.add(new MaxSinglePrimarySizeCondition(step.getMaxSinglePrimarySize()));
+            }
+            if (step.getMaxAge() != null) {
+                expectedConditions.add(new MaxAgeCondition(step.getMaxAge()));
+            }
             if (step.getMaxDocs() != null) {
                 expectedConditions.add(new MaxDocsCondition(step.getMaxDocs()));
             }
@@ -388,12 +403,15 @@ public class WaitForRolloverReadyStepTests extends AbstractStepTestCase<WaitForR
             @SuppressWarnings("unchecked")
             ActionListener<RolloverResponse> listener = (ActionListener<RolloverResponse>) invocation.getArguments()[1];
             Set<Condition<?>> expectedConditions = new HashSet<>();
-            if (step.getMaxAge() != null) {
-                expectedConditions.add(new MaxAgeCondition(step.getMaxAge()));
-            }
             if (step.getMaxSize() != null) {
                 expectedConditions.add(new MaxSizeCondition(step.getMaxSize()));
             }
+            if (step.getMaxSinglePrimarySize() != null) {
+                expectedConditions.add(new MaxSinglePrimarySizeCondition(step.getMaxSinglePrimarySize()));
+            }
+            if (step.getMaxAge() != null) {
+                expectedConditions.add(new MaxAgeCondition(step.getMaxAge()));
+            }
             if (step.getMaxDocs() != null) {
                 expectedConditions.add(new MaxDocsCondition(step.getMaxDocs()));
             }
@@ -439,12 +457,15 @@ public class WaitForRolloverReadyStepTests extends AbstractStepTestCase<WaitForR
             @SuppressWarnings("unchecked")
             ActionListener<RolloverResponse> listener = (ActionListener<RolloverResponse>) invocation.getArguments()[1];
             Set<Condition<?>> expectedConditions = new HashSet<>();
-            if (step.getMaxAge() != null) {
-                expectedConditions.add(new MaxAgeCondition(step.getMaxAge()));
-            }
             if (step.getMaxSize() != null) {
                 expectedConditions.add(new MaxSizeCondition(step.getMaxSize()));
             }
+            if (step.getMaxSinglePrimarySize() != null) {
+                expectedConditions.add(new MaxSinglePrimarySizeCondition(step.getMaxSinglePrimarySize()));
+            }
+            if (step.getMaxAge() != null) {
+                expectedConditions.add(new MaxAgeCondition(step.getMaxAge()));
+            }
             if (step.getMaxDocs() != null) {
                 expectedConditions.add(new MaxDocsCondition(step.getMaxDocs()));
             }

+ 1 - 1
x-pack/plugin/ilm/qa/multi-node/src/javaRestTest/java/org/elasticsearch/xpack/TimeSeriesRestDriver.java

@@ -169,7 +169,7 @@ public final class TimeSeriesRestDriver {
     public static void createFullPolicy(RestClient client, String policyName, TimeValue hotTime) throws IOException {
         Map<String, LifecycleAction> hotActions = new HashMap<>();
         hotActions.put(SetPriorityAction.NAME, new SetPriorityAction(100));
-        hotActions.put(RolloverAction.NAME, new RolloverAction(null, null, 1L));
+        hotActions.put(RolloverAction.NAME, new RolloverAction(null, null, null, 1L));
         Map<String, LifecycleAction> warmActions = new HashMap<>();
         warmActions.put(SetPriorityAction.NAME, new SetPriorityAction(50));
         warmActions.put(ForceMergeAction.NAME, new ForceMergeAction(1, null));

+ 2 - 2
x-pack/plugin/ilm/qa/multi-node/src/javaRestTest/java/org/elasticsearch/xpack/ilm/ChangePolicyforIndexIT.java

@@ -58,12 +58,12 @@ public class ChangePolicyforIndexIT extends ESRestTestCase {
         String indexName = "test-000001";
         // create policy_1 and policy_2
         Map<String, Phase> phases1 = new HashMap<>();
-        phases1.put("hot", new Phase("hot", TimeValue.ZERO, singletonMap(RolloverAction.NAME, new RolloverAction(null, null, 1L))));
+        phases1.put("hot", new Phase("hot", TimeValue.ZERO, singletonMap(RolloverAction.NAME, new RolloverAction(null, null, null, 1L))));
         phases1.put("warm", new Phase("warm", TimeValue.ZERO,
                 singletonMap(AllocateAction.NAME, new AllocateAction(1, singletonMap("_name", "foobarbaz"), null, null))));
         LifecyclePolicy lifecyclePolicy1 = new LifecyclePolicy("policy_1", phases1);
         Map<String, Phase> phases2 = new HashMap<>();
-        phases2.put("hot", new Phase("hot", TimeValue.ZERO, singletonMap(RolloverAction.NAME, new RolloverAction(null, null, 1000L))));
+        phases2.put("hot", new Phase("hot", TimeValue.ZERO, singletonMap(RolloverAction.NAME, new RolloverAction(null, null, null, 1000L))));
         phases2.put("warm", new Phase("warm", TimeValue.ZERO,
                 singletonMap(AllocateAction.NAME,
                     new AllocateAction(1, singletonMap("_name", "javaRestTest-0,javaRestTest-1,javaRestTest-2,javaRestTest-3"),

+ 2 - 2
x-pack/plugin/ilm/qa/multi-node/src/javaRestTest/java/org/elasticsearch/xpack/ilm/TimeSeriesDataStreamsIT.java

@@ -66,7 +66,7 @@ public class TimeSeriesDataStreamsIT extends ESRestTestCase {
 
 
     public void testRolloverAction() throws Exception {
-        createNewSingletonPolicy(client(), policyName, "hot", new RolloverAction(null, null, 1L));
+        createNewSingletonPolicy(client(), policyName, "hot", new RolloverAction(null, null, null, 1L));
 
         createComposableTemplate(client(), template, dataStream + "*", getTemplate(policyName));
 
@@ -80,7 +80,7 @@ public class TimeSeriesDataStreamsIT extends ESRestTestCase {
     }
 
     public void testRolloverIsSkippedOnManualDataStreamRollover() throws Exception {
-        createNewSingletonPolicy(client(), policyName, "hot", new RolloverAction(null, null, 2L));
+        createNewSingletonPolicy(client(), policyName, "hot", new RolloverAction(null, null, null, 2L));
 
         createComposableTemplate(client(), template, dataStream + "*", getTemplate(policyName));
 

+ 55 - 27
x-pack/plugin/ilm/qa/multi-node/src/javaRestTest/java/org/elasticsearch/xpack/ilm/TimeSeriesLifecycleActionsIT.java

@@ -20,6 +20,7 @@ import org.elasticsearch.common.CheckedRunnable;
 import org.elasticsearch.common.Nullable;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.unit.ByteSizeValue;
 import org.elasticsearch.common.unit.TimeValue;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentHelper;
@@ -171,15 +172,18 @@ public class TimeSeriesLifecycleActionsIT extends ESRestTestCase {
             .put(RolloverAction.LIFECYCLE_ROLLOVER_ALIAS, alias));
 
         // create policy
-        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, 1L));
+        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, null, 1L));
         // update policy on index
         updatePolicy(client(), originalIndex, policy);
         // index document {"foo": "bar"} to trigger rollover
         index(client(), originalIndex, "_id", "foo", "bar");
-        assertBusy(() -> assertTrue(indexExists(secondIndex)));
-        assertBusy(() -> assertTrue(indexExists(originalIndex)));
-        assertBusy(() -> assertEquals("true",
-            getOnlyIndexSettings(client(), originalIndex).get(LifecycleSettings.LIFECYCLE_INDEXING_COMPLETE)));
+
+        assertBusy(() -> {
+            assertThat(getStepKeyForIndex(client(), originalIndex), equalTo(PhaseCompleteStep.finalStep("hot").getKey()));
+            assertTrue(indexExists(secondIndex));
+            assertTrue(indexExists(originalIndex));
+            assertEquals("true", getOnlyIndexSettings(client(), originalIndex).get(LifecycleSettings.LIFECYCLE_INDEXING_COMPLETE));
+        }, 30, TimeUnit.SECONDS);
     }
 
     public void testRolloverActionWithIndexingComplete() throws Exception {
@@ -211,16 +215,41 @@ public class TimeSeriesLifecycleActionsIT extends ESRestTestCase {
         client().performRequest(updateAliasRequest);
 
         // create policy
-        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, 1L));
+        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, null, 1L));
         // update policy on index
         updatePolicy(client(), originalIndex, policy);
         // index document {"foo": "bar"} to trigger rollover
         index(client(), originalIndex, "_id", "foo", "bar");
-        assertBusy(() -> assertThat(getStepKeyForIndex(client(), originalIndex), equalTo(PhaseCompleteStep.finalStep("hot").getKey())));
-        assertBusy(() -> assertTrue(indexExists(originalIndex)));
-        assertBusy(() -> assertFalse(indexExists(secondIndex)));
-        assertBusy(() -> assertEquals("true",
-            getOnlyIndexSettings(client(), originalIndex).get(LifecycleSettings.LIFECYCLE_INDEXING_COMPLETE)));
+
+        assertBusy(() -> {
+            assertThat(getStepKeyForIndex(client(), originalIndex), equalTo(PhaseCompleteStep.finalStep("hot").getKey()));
+            assertTrue(indexExists(originalIndex));
+            assertFalse(indexExists(secondIndex)); // careful, *assertFalse* not *assertTrue*
+            assertEquals("true", getOnlyIndexSettings(client(), originalIndex).get(LifecycleSettings.LIFECYCLE_INDEXING_COMPLETE));
+        }, 30, TimeUnit.SECONDS);
+    }
+
+    public void testRolloverActionWithMaxSinglePrimarySize() throws Exception {
+        String originalIndex = index + "-000001";
+        String secondIndex = index + "-000002";
+        createIndexWithSettings(client(), originalIndex, alias, Settings.builder()
+            .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 3)
+            .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0)
+            .put(RolloverAction.LIFECYCLE_ROLLOVER_ALIAS, alias));
+
+        index(client(), originalIndex, "_id", "foo", "bar");
+
+        // create policy
+        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, ByteSizeValue.ofBytes(1), null, null));
+        // update policy on index
+        updatePolicy(client(), originalIndex, policy);
+
+        assertBusy(() -> {
+            assertThat(getStepKeyForIndex(client(), originalIndex), equalTo(PhaseCompleteStep.finalStep("hot").getKey()));
+            assertTrue(indexExists(secondIndex));
+            assertTrue(indexExists(originalIndex));
+            assertEquals("true", getOnlyIndexSettings(client(), originalIndex).get(LifecycleSettings.LIFECYCLE_INDEXING_COMPLETE));
+        }, 30, TimeUnit.SECONDS);
     }
 
     public void testAllocateOnlyAllocation() throws Exception {
@@ -260,7 +289,7 @@ public class TimeSeriesLifecycleActionsIT extends ESRestTestCase {
         String slmPolicy = randomAlphaOfLengthBetween(4, 10);
         createNewSingletonPolicy(client(), policy, "delete", new WaitForSnapshotAction(slmPolicy));
         updatePolicy(client(), index, policy);
-        assertBusy( () -> {
+        assertBusy(() -> {
             Map<String, Object> indexILMState = explainIndex(client(), index);
             assertThat(indexILMState.get("action"), is("wait_for_snapshot"));
             assertThat(indexILMState.get("failed_step"), is("wait-for-snapshot"));
@@ -270,7 +299,7 @@ public class TimeSeriesLifecycleActionsIT extends ESRestTestCase {
         createSnapshotRepo(client(), snapshotRepo, randomBoolean());
         createSlmPolicy(slmPolicy, snapshotRepo);
 
-        assertBusy( () -> {
+        assertBusy(() -> {
             Map<String, Object> indexILMState = explainIndex(client(), index);
             //wait for step to notice that the slm policy is created and to get out of error
             assertThat(indexILMState.get("failed_step"), nullValue());
@@ -308,7 +337,7 @@ public class TimeSeriesLifecycleActionsIT extends ESRestTestCase {
 
         updatePolicy(client(), index, policy);
 
-        assertBusy( () -> {
+        assertBusy(() -> {
             Map<String, Object> indexILMState = explainIndex(client(), index);
             assertThat(indexILMState.get("failed_step"), nullValue());
             assertThat(indexILMState.get("action"), is("wait_for_snapshot"));
@@ -418,7 +447,6 @@ public class TimeSeriesLifecycleActionsIT extends ESRestTestCase {
         forceMergeActionWithCodec("best_compression");
     }
 
-
     public void testFreezeAction() throws Exception {
         createIndexWithSettings(client(), index, alias, Settings.builder().put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1)
             .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0));
@@ -518,7 +546,7 @@ public class TimeSeriesLifecycleActionsIT extends ESRestTestCase {
         client().performRequest(templateRequest);
 
         policy = randomAlphaOfLengthBetween(5,20);
-        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, 1L));
+        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, null, 1L));
 
         index = indexPrefix + "-000001";
         final StringEntity putIndex = new StringEntity("{\n" +
@@ -611,7 +639,7 @@ public class TimeSeriesLifecycleActionsIT extends ESRestTestCase {
         String originalIndex = index + "-000001";
         String secondIndex = index + "-000002";
         // Set up a policy with rollover
-        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, 1L));
+        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, null, 1L));
         createIndexWithSettings(
             client(),
             originalIndex,
@@ -672,7 +700,7 @@ public class TimeSeriesLifecycleActionsIT extends ESRestTestCase {
     public void testILMRolloverRetriesOnReadOnlyBlock() throws Exception {
         String firstIndex = index + "-000001";
 
-        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, TimeValue.timeValueSeconds(1), null));
+        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, TimeValue.timeValueSeconds(1), null));
 
         // create the index as readonly and associate the ILM policy to it
         createIndexWithSettings(
@@ -710,7 +738,7 @@ public class TimeSeriesLifecycleActionsIT extends ESRestTestCase {
         String thirdIndex = index + "-000003";
 
         // Set up a policy with rollover
-        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, 2L));
+        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, null, 2L));
         Request createIndexTemplate = new Request("PUT", "_template/rolling_indexes");
         createIndexTemplate.setJsonEntity("{" +
             "\"index_patterns\": [\"" + index + "-*\"], \n" +
@@ -767,7 +795,7 @@ public class TimeSeriesLifecycleActionsIT extends ESRestTestCase {
         String index = this.index + "-000001";
         String rolledIndex = this.index + "-000002";
 
-        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, TimeValue.timeValueSeconds(1), null));
+        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, TimeValue.timeValueSeconds(1), null));
 
         // create the rolled index so the rollover of the first index fails
         createIndexWithSettings(
@@ -844,7 +872,7 @@ public class TimeSeriesLifecycleActionsIT extends ESRestTestCase {
     public void testUpdateRolloverLifecycleDateStepRetriesWhenRolloverInfoIsMissing() throws Exception {
         String index = this.index + "-000001";
 
-        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, 1L));
+        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, null, 1L));
 
         createIndexWithSettings(
             client(),
@@ -898,7 +926,7 @@ public class TimeSeriesLifecycleActionsIT extends ESRestTestCase {
             true);
 
         // create policy
-        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, 1L));
+        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, null, 1L));
         // update policy on index
         updatePolicy(client(), originalIndex, policy);
         Request createIndexTemplate = new Request("PUT", "_template/rolling_indexes");
@@ -925,7 +953,7 @@ public class TimeSeriesLifecycleActionsIT extends ESRestTestCase {
     }
 
     public void testHistoryIsWrittenWithSuccess() throws Exception {
-        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, 1L));
+        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, null, 1L));
         Request createIndexTemplate = new Request("PUT", "_template/rolling_indexes");
         createIndexTemplate.setJsonEntity("{" +
             "\"index_patterns\": [\""+ index + "-*\"], \n" +
@@ -957,7 +985,7 @@ public class TimeSeriesLifecycleActionsIT extends ESRestTestCase {
 
     public void testHistoryIsWrittenWithFailure() throws Exception {
         createIndexWithSettings(client(), index + "-1", alias, Settings.builder(), false);
-        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, 1L));
+        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, null, 1L));
         updatePolicy(client(), index + "-1", policy);
 
         // Index a document
@@ -1028,7 +1056,7 @@ public class TimeSeriesLifecycleActionsIT extends ESRestTestCase {
     public void testRefreshablePhaseJson() throws Exception {
         String index = "refresh-index";
 
-        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, 100L));
+        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, null, 100L));
         Request createIndexTemplate = new Request("PUT", "_template/rolling_indexes");
         createIndexTemplate.setJsonEntity("{" +
             "\"index_patterns\": [\""+ index + "-*\"], \n" +
@@ -1053,7 +1081,7 @@ public class TimeSeriesLifecycleActionsIT extends ESRestTestCase {
         assertBusy(() -> assertThat(getStepKeyForIndex(client(), index + "-1").getName(), equalTo(WaitForRolloverReadyStep.NAME)));
 
         // Update the policy to allow rollover at 1 document instead of 100
-        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, 1L));
+        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, null, 1L));
 
         // Index should now have been able to roll over, creating the new index and proceeding to the "complete" step
         assertBusy(() -> assertThat(indexExists(index + "-000002"), is(true)));
@@ -1131,7 +1159,7 @@ public class TimeSeriesLifecycleActionsIT extends ESRestTestCase {
         assertTrue(waitUntil(() -> {
             try {
                 Map<String, Object> explainIndex = explainIndex(client(), index);
-                if(explainIndex == null) {
+                if (explainIndex == null) {
                     // in case we missed the original index and it was deleted
                     explainIndex = explainIndex(client(), restoredIndexName);
                 }

+ 2 - 2
x-pack/plugin/ilm/qa/multi-node/src/javaRestTest/java/org/elasticsearch/xpack/ilm/TimeseriesMoveToStepIT.java

@@ -161,7 +161,7 @@ public class TimeseriesMoveToStepIT extends ESRestTestCase {
     }
 
     public void testMoveToStepRereadsPolicy() throws Exception {
-        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, TimeValue.timeValueHours(1), null), TimeValue.ZERO);
+        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, TimeValue.timeValueHours(1), null), TimeValue.ZERO);
 
         createIndexWithSettings(client(), "test-1", alias, Settings.builder()
                 .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1)
@@ -173,7 +173,7 @@ public class TimeseriesMoveToStepIT extends ESRestTestCase {
         assertBusy(() -> assertThat(getStepKeyForIndex(client(), "test-1"),
             equalTo(new StepKey("hot", "rollover", "check-rollover-ready"))), 30, TimeUnit.SECONDS);
 
-        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, 1L), TimeValue.ZERO);
+        createNewSingletonPolicy(client(), policy, "hot", new RolloverAction(null, null, null, 1L), TimeValue.ZERO);
 
         // Move to the same step, which should re-read the policy
         Request moveToStepRequest = new Request("POST", "_ilm/move/test-1");

+ 1 - 1
x-pack/plugin/ilm/qa/multi-node/src/javaRestTest/java/org/elasticsearch/xpack/ilm/actions/ReadonlyActionIT.java

@@ -69,7 +69,7 @@ public class ReadonlyActionIT extends ESRestTestCase {
 
         // add a policy
         Map<String, LifecycleAction> hotActions = Map.of(
-            RolloverAction.NAME, new RolloverAction(null, null, 1L),
+            RolloverAction.NAME, new RolloverAction(null, null, null, 1L),
             ReadOnlyAction.NAME, new ReadOnlyAction());
         Map<String, Phase> phases = Map.of(
             "hot", new Phase("hot", TimeValue.ZERO, hotActions));

+ 5 - 5
x-pack/plugin/ilm/qa/multi-node/src/javaRestTest/java/org/elasticsearch/xpack/ilm/actions/SearchableSnapshotActionIT.java

@@ -220,7 +220,7 @@ public class SearchableSnapshotActionIT extends ESRestTestCase {
 
     public void testCreateInvalidPolicy() {
         IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> createPolicy(client(), policy,
-            new Phase("hot", TimeValue.ZERO, Map.of(RolloverAction.NAME, new RolloverAction(null, null, 1L), SearchableSnapshotAction.NAME,
+            new Phase("hot", TimeValue.ZERO, Map.of(RolloverAction.NAME, new RolloverAction(null, null, null, 1L), SearchableSnapshotAction.NAME,
                 new SearchableSnapshotAction(randomAlphaOfLengthBetween(4, 10)))),
             new Phase("warm", TimeValue.ZERO, Map.of(ForceMergeAction.NAME, new ForceMergeAction(1, null))),
             new Phase("cold", TimeValue.ZERO, Map.of(FreezeAction.NAME, new FreezeAction())),
@@ -235,7 +235,7 @@ public class SearchableSnapshotActionIT extends ESRestTestCase {
     public void testUpdatePolicyToAddPhasesYieldsInvalidActionsToBeSkipped() throws Exception {
         createSnapshotRepo(client(), snapshotRepo, randomBoolean());
         createPolicy(client(), policy,
-            new Phase("hot", TimeValue.ZERO, Map.of(RolloverAction.NAME, new RolloverAction(null, null, 1L), SearchableSnapshotAction.NAME,
+            new Phase("hot", TimeValue.ZERO, Map.of(RolloverAction.NAME, new RolloverAction(null, null, null, 1L), SearchableSnapshotAction.NAME,
                 new SearchableSnapshotAction(snapshotRepo))),
             new Phase("warm", TimeValue.timeValueDays(30), Map.of(SetPriorityAction.NAME, new SetPriorityAction(999))),
             null, null, null
@@ -290,7 +290,7 @@ public class SearchableSnapshotActionIT extends ESRestTestCase {
         // let's create a data stream, rollover it and convert the first generation backing index into a searchable snapshot
         createSnapshotRepo(client(), snapshotRepo, randomBoolean());
         createPolicy(client(), policy,
-            new Phase("hot", TimeValue.ZERO, Map.of(RolloverAction.NAME, new RolloverAction(null, null, 1L),
+            new Phase("hot", TimeValue.ZERO, Map.of(RolloverAction.NAME, new RolloverAction(null, null, null, 1L),
                 SearchableSnapshotAction.NAME, new SearchableSnapshotAction(snapshotRepo))),
             new Phase("warm", TimeValue.timeValueDays(30), Map.of(SetPriorityAction.NAME, new SetPriorityAction(999))),
             null, null, null
@@ -474,7 +474,7 @@ public class SearchableSnapshotActionIT extends ESRestTestCase {
                 Map.of(
                     SearchableSnapshotAction.NAME, new SearchableSnapshotAction(snapshotRepo, randomBoolean(),
                     MountSearchableSnapshotRequest.Storage.SHARED_CACHE),
-                    RolloverAction.NAME, new RolloverAction(null, null, 1L))),
+                    RolloverAction.NAME, new RolloverAction(null, null, null, 1L))),
             null, null,
             new Phase("frozen", TimeValue.ZERO,
                 singletonMap(SearchableSnapshotAction.NAME, new SearchableSnapshotAction(snapshotRepo, randomBoolean(),
@@ -546,7 +546,7 @@ public class SearchableSnapshotActionIT extends ESRestTestCase {
     public void testSearchableSnapshotActionOverridesMigrateAction() throws Exception {
         createSnapshotRepo(client(), snapshotRepo, randomBoolean());
         createPolicy(client(), policy,
-            new Phase("hot", TimeValue.ZERO, Map.of(RolloverAction.NAME, new RolloverAction(null, null, 1L),
+            new Phase("hot", TimeValue.ZERO, Map.of(RolloverAction.NAME, new RolloverAction(null, null, null, 1L),
                 SearchableSnapshotAction.NAME, new SearchableSnapshotAction(
                     snapshotRepo, randomBoolean(), MountSearchableSnapshotRequest.Storage.FULL_COPY))
             ),

+ 1 - 1
x-pack/plugin/ilm/qa/multi-node/src/javaRestTest/java/org/elasticsearch/xpack/ilm/actions/ShrinkActionIT.java

@@ -161,7 +161,7 @@ public class ShrinkActionIT extends ESRestTestCase {
 
         // add a policy
         Map<String, LifecycleAction> hotActions = Map.of(
-            RolloverAction.NAME, new RolloverAction(null, null, 1L),
+            RolloverAction.NAME, new RolloverAction(null, null, null, 1L),
             ShrinkAction.NAME, new ShrinkAction(expectedFinalShards, null));
         Map<String, Phase> phases = Map.of(
             "hot", new Phase("hot", TimeValue.ZERO, hotActions));

+ 1 - 1
x-pack/plugin/ilm/qa/with-security/src/javaRestTest/java/org/elasticsearch/xpack/security/PermissionsIT.java

@@ -284,7 +284,7 @@ public class PermissionsIT extends ESRestTestCase {
          * - Create role with just write and manage privileges on alias
          * - Create user and assign newly created role.
          */
-        createNewSingletonPolicy(adminClient(), "foo-policy", "hot", new RolloverAction(null, null, 2L));
+        createNewSingletonPolicy(adminClient(), "foo-policy", "hot", new RolloverAction(null, null, null, 2L));
         createIndexTemplate("foo-template", "foo-logs-*", "foo_alias", "foo-policy");
         createIndexAsAdmin("foo-logs-000001", "foo_alias", randomBoolean());
         createRole("foo_alias_role", "foo_alias");

+ 1 - 1
x-pack/plugin/ilm/src/internalClusterTest/java/org/elasticsearch/xpack/ilm/ILMMultiNodeIT.java

@@ -74,7 +74,7 @@ public class ILMMultiNodeIT extends ESIntegTestCase {
         startWarmOnlyNode();
         ensureGreen();
 
-        RolloverAction rolloverAction = new RolloverAction(null, null, 1L);
+        RolloverAction rolloverAction = new RolloverAction(null, null, null, 1L);
         Phase hotPhase = new Phase("hot", TimeValue.ZERO, Collections.singletonMap(rolloverAction.getWriteableName(), rolloverAction));
         ShrinkAction shrinkAction = new ShrinkAction(1, null);
         Phase warmPhase = new Phase("warm", TimeValue.ZERO, Collections.singletonMap(shrinkAction.getWriteableName(), shrinkAction));

+ 8 - 8
x-pack/plugin/ilm/src/test/java/org/elasticsearch/xpack/ilm/action/TransportPutLifecycleActionTests.java

@@ -206,7 +206,7 @@ public class TransportPutLifecycleActionTests extends ESTestCase {
                 .build();
 
             Map<String, LifecycleAction> actions = new HashMap<>();
-            actions.put("rollover", new RolloverAction(null, null, 1L));
+            actions.put("rollover", new RolloverAction(null, null, null, 1L));
             actions.put("set_priority", new SetPriorityAction(100));
             Phase hotPhase = new Phase("hot", TimeValue.ZERO, actions);
             Map<String, Phase> phases = Collections.singletonMap("hot", hotPhase);
@@ -281,7 +281,7 @@ public class TransportPutLifecycleActionTests extends ESTestCase {
                 .build();
 
             Map<String, LifecycleAction> actions = new HashMap<>();
-            actions.put("rollover", new RolloverAction(null, TimeValue.timeValueSeconds(5), null));
+            actions.put("rollover", new RolloverAction(null, null, TimeValue.timeValueSeconds(5), null));
             Phase hotPhase = new Phase("hot", TimeValue.ZERO, actions);
             Map<String, Phase> phases = Collections.singletonMap("hot", hotPhase);
             LifecyclePolicy newPolicy = new LifecyclePolicy("my-policy", phases);
@@ -315,7 +315,7 @@ public class TransportPutLifecycleActionTests extends ESTestCase {
                 .build();
 
             Map<String, LifecycleAction> actions = new HashMap<>();
-            actions.put("rollover", new RolloverAction(null, null, 1L));
+            actions.put("rollover", new RolloverAction(null, null, null, 1L));
             actions.put("set_priority", new SetPriorityAction(100));
             Phase hotPhase = new Phase("hot", TimeValue.ZERO, actions);
             Map<String, Phase> phases = Collections.singletonMap("hot", hotPhase);
@@ -338,7 +338,7 @@ public class TransportPutLifecycleActionTests extends ESTestCase {
                 .build();
 
             Map<String, LifecycleAction> actions = new HashMap<>();
-            actions.put("rollover", new RolloverAction(null, null, 1L));
+            actions.put("rollover", new RolloverAction(null, null, null, 1L));
             actions.put("set_priority", new SetPriorityAction(100));
             Phase hotPhase = new Phase("hot", TimeValue.ZERO, actions);
             Map<String, Phase> phases = Collections.singletonMap("hot", hotPhase);
@@ -376,7 +376,7 @@ public class TransportPutLifecycleActionTests extends ESTestCase {
             .build();
 
         Map<String, LifecycleAction> actions = new HashMap<>();
-        actions.put("rollover", new RolloverAction(null, null, 1L));
+        actions.put("rollover", new RolloverAction(null, null, null, 1L));
         actions.put("set_priority", new SetPriorityAction(100));
         Phase hotPhase = new Phase("hot", TimeValue.ZERO, actions);
         Map<String, Phase> phases = Collections.singletonMap("hot", hotPhase);
@@ -422,14 +422,14 @@ public class TransportPutLifecycleActionTests extends ESTestCase {
         assertTrue(TransportPutLifecycleAction.eligibleToCheckForRefresh(meta));
 
         Map<String, LifecycleAction> oldActions = new HashMap<>();
-        oldActions.put("rollover", new RolloverAction(null, null, 1L));
+        oldActions.put("rollover", new RolloverAction(null, null, null, 1L));
         oldActions.put("set_priority", new SetPriorityAction(100));
         Phase oldHotPhase = new Phase("hot", TimeValue.ZERO, oldActions);
         Map<String, Phase> oldPhases = Collections.singletonMap("hot", oldHotPhase);
         LifecyclePolicy oldPolicy = new LifecyclePolicy("my-policy", oldPhases);
 
         Map<String, LifecycleAction> actions = new HashMap<>();
-        actions.put("rollover", new RolloverAction(null, null, 1L));
+        actions.put("rollover", new RolloverAction(null, null, null, 1L));
         actions.put("set_priority", new SetPriorityAction(100));
         Phase hotPhase = new Phase("hot", TimeValue.ZERO, actions);
         Map<String, Phase> phases = Collections.singletonMap("hot", hotPhase);
@@ -452,7 +452,7 @@ public class TransportPutLifecycleActionTests extends ESTestCase {
         assertThat(updatedState, equalTo(existingState));
 
         actions = new HashMap<>();
-        actions.put("rollover", new RolloverAction(null, null, 2L));
+        actions.put("rollover", new RolloverAction(null, null, null, 2L));
         actions.put("set_priority", new SetPriorityAction(150));
         hotPhase = new Phase("hot", TimeValue.ZERO, actions);
         phases = Collections.singletonMap("hot", hotPhase);