Bladeren bron

Include in-progress snapshot for a policy with get SLM policy… (#45245)

This commit adds the "in_progress" key to the SLM get policy API,
returning a policy that looks like:

```json
{
  "daily-snapshots" : {
    "version" : 1,
    "modified_date" : "2019-08-05T18:41:48.778Z",
    "modified_date_millis" : 1565030508778,
    "policy" : {
      "name" : "<production-snap-{now/d}>",
      "schedule" : "0 30 1 * * ?",
      "repository" : "repo",
      "config" : {
        "indices" : [
          "foo-*",
          "important"
        ],
        "ignore_unavailable" : true,
        "include_global_state" : false
      },
      "retention" : {
        "expire_after" : "10m"
      }
    },
    "last_success" : {
      "snapshot_name" : "production-snap-2019.08.05-oxctmnobqye3luim4uejhg",
      "time_string" : "2019-08-05T18:42:23.257Z",
      "time" : 1565030543257
    },
    "next_execution" : "2019-08-06T01:30:00.000Z",
    "next_execution_millis" : 1565055000000,
    "in_progress" : {
      "name" : "production-snap-2019.08.05-oxctmnobqye3luim4uejhg",
      "uuid" : "t8Idqt6JQxiZrzp0Vt7z6g",
      "state" : "STARTED",
      "start_time" : "2019-08-05T18:42:22.998Z",
      "start_time_millis" : 1565030542998
    }
  }
}
```

These are only visible while the snapshot is being taken (or failed),
since it reads from the cluster state rather than from the repository
itself.
Lee Hinman 6 jaren geleden
bovenliggende
commit
177bd62aba

+ 99 - 2
client/rest-high-level/src/main/java/org/elasticsearch/client/slm/SnapshotLifecyclePolicyMetadata.java

@@ -21,10 +21,12 @@ package org.elasticsearch.client.slm;
 
 import org.elasticsearch.common.Nullable;
 import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.xcontent.ConstructingObjectParser;
 import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.snapshots.SnapshotId;
 
 import java.io.IOException;
 import java.util.Objects;
@@ -39,6 +41,7 @@ public class SnapshotLifecyclePolicyMetadata implements ToXContentObject {
     static final ParseField LAST_FAILURE = new ParseField("last_failure");
     static final ParseField NEXT_EXECUTION_MILLIS = new ParseField("next_execution_millis");
     static final ParseField NEXT_EXECUTION = new ParseField("next_execution");
+    static final ParseField SNAPSHOT_IN_PROGRESS = new ParseField("in_progress");
 
     private final SnapshotLifecyclePolicy policy;
     private final long version;
@@ -48,6 +51,8 @@ public class SnapshotLifecyclePolicyMetadata implements ToXContentObject {
     private final SnapshotInvocationRecord lastSuccess;
     @Nullable
     private final SnapshotInvocationRecord lastFailure;
+    @Nullable
+    private final SnapshotInProgress snapshotInProgress;
 
     @SuppressWarnings("unchecked")
     public static final ConstructingObjectParser<SnapshotLifecyclePolicyMetadata, String> PARSER =
@@ -59,8 +64,9 @@ public class SnapshotLifecyclePolicyMetadata implements ToXContentObject {
                 SnapshotInvocationRecord lastSuccess = (SnapshotInvocationRecord) a[3];
                 SnapshotInvocationRecord lastFailure = (SnapshotInvocationRecord) a[4];
                 long nextExecution = (long) a[5];
+                SnapshotInProgress sip = (SnapshotInProgress) a[6];
 
-                return new SnapshotLifecyclePolicyMetadata(policy, version, modifiedDate, lastSuccess, lastFailure, nextExecution);
+                return new SnapshotLifecyclePolicyMetadata(policy, version, modifiedDate, lastSuccess, lastFailure, nextExecution, sip);
             });
 
     static {
@@ -70,6 +76,7 @@ public class SnapshotLifecyclePolicyMetadata implements ToXContentObject {
         PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), SnapshotInvocationRecord::parse, LAST_SUCCESS);
         PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), SnapshotInvocationRecord::parse, LAST_FAILURE);
         PARSER.declareLong(ConstructingObjectParser.constructorArg(), NEXT_EXECUTION_MILLIS);
+        PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), SnapshotInProgress::parse, SNAPSHOT_IN_PROGRESS);
     }
 
     public static SnapshotLifecyclePolicyMetadata parse(XContentParser parser, String id) {
@@ -78,13 +85,15 @@ public class SnapshotLifecyclePolicyMetadata implements ToXContentObject {
 
     public SnapshotLifecyclePolicyMetadata(SnapshotLifecyclePolicy policy, long version, long modifiedDate,
                                            SnapshotInvocationRecord lastSuccess, SnapshotInvocationRecord lastFailure,
-                                           long nextExecution) {
+                                           long nextExecution,
+                                           @Nullable SnapshotInProgress snapshotInProgress) {
         this.policy = policy;
         this.version = version;
         this.modifiedDate = modifiedDate;
         this.lastSuccess = lastSuccess;
         this.lastFailure = lastFailure;
         this.nextExecution = nextExecution;
+        this.snapshotInProgress = snapshotInProgress;
     }
 
     public SnapshotLifecyclePolicy getPolicy() {
@@ -115,6 +124,11 @@ public class SnapshotLifecyclePolicyMetadata implements ToXContentObject {
         return this.nextExecution;
     }
 
+    @Nullable
+    public SnapshotInProgress getSnapshotInProgress() {
+        return this.snapshotInProgress;
+    }
+
     @Override
     public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
         builder.startObject();
@@ -128,6 +142,9 @@ public class SnapshotLifecyclePolicyMetadata implements ToXContentObject {
             builder.field(LAST_FAILURE.getPreferredName(), lastFailure);
         }
         builder.timeField(NEXT_EXECUTION_MILLIS.getPreferredName(), NEXT_EXECUTION.getPreferredName(), nextExecution);
+        if (snapshotInProgress != null) {
+            builder.field(SNAPSHOT_IN_PROGRESS.getPreferredName(), snapshotInProgress);
+        }
         builder.endObject();
         return builder;
     }
@@ -154,4 +171,84 @@ public class SnapshotLifecyclePolicyMetadata implements ToXContentObject {
             Objects.equals(nextExecution, other.nextExecution);
     }
 
+    public static class SnapshotInProgress implements ToXContentObject {
+        private static final ParseField NAME = new ParseField("name");
+        private static final ParseField UUID = new ParseField("uuid");
+        private static final ParseField STATE = new ParseField("state");
+        private static final ParseField START_TIME = new ParseField("start_time_millis");
+        private static final ParseField FAILURE = new ParseField("failure");
+
+        private static final ConstructingObjectParser<SnapshotInProgress, Void> PARSER =
+            new ConstructingObjectParser<>("snapshot_in_progress", true, a -> {
+                SnapshotId id = new SnapshotId((String) a[0], (String) a[1]);
+                String state = (String) a[2];
+                long start = (long) a[3];
+                String failure = (String) a[4];
+                return new SnapshotInProgress(id, state, start, failure);
+            });
+
+        static {
+            PARSER.declareString(ConstructingObjectParser.constructorArg(), NAME);
+            PARSER.declareString(ConstructingObjectParser.constructorArg(), UUID);
+            PARSER.declareString(ConstructingObjectParser.constructorArg(), STATE);
+            PARSER.declareLong(ConstructingObjectParser.constructorArg(), START_TIME);
+            PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), FAILURE);
+        }
+
+        private final SnapshotId snapshotId;
+        private final String state;
+        private final long startTime;
+        private final String failure;
+
+        public SnapshotInProgress(SnapshotId snapshotId, String state, long startTime, @Nullable String failure) {
+            this.snapshotId = snapshotId;
+            this.state = state;
+            this.startTime = startTime;
+            this.failure = failure;
+        }
+
+        private static SnapshotInProgress parse(XContentParser parser, String name) {
+            return PARSER.apply(parser, null);
+        }
+
+        @Override
+        public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+            builder.startObject();
+            builder.field(NAME.getPreferredName(), snapshotId.getName());
+            builder.field(UUID.getPreferredName(), snapshotId.getUUID());
+            builder.field(STATE.getPreferredName(), state);
+            builder.timeField(START_TIME.getPreferredName(), "start_time", startTime);
+            if (failure != null) {
+                builder.field(FAILURE.getPreferredName(), failure);
+            }
+            builder.endObject();
+            return builder;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(snapshotId, state, startTime, failure);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == null) {
+                return false;
+            }
+
+            if (obj.getClass() != getClass()) {
+                return false;
+            }
+            SnapshotInProgress other = (SnapshotInProgress) obj;
+            return Objects.equals(snapshotId, other.snapshotId) &&
+                Objects.equals(state, other.state) &&
+                startTime == other.startTime &&
+                Objects.equals(failure, other.failure);
+        }
+
+        @Override
+        public String toString() {
+            return Strings.toString(this);
+        }
+    }
 }

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

@@ -854,6 +854,8 @@ public class ILMDocumentationIT extends ESRestHighLevelClientTestCase {
         long nextPolicyExecutionDate = policyMeta.getNextExecution();
         SnapshotInvocationRecord lastSuccess = policyMeta.getLastSuccess();
         SnapshotInvocationRecord lastFailure = policyMeta.getLastFailure();
+        SnapshotLifecyclePolicyMetadata.SnapshotInProgress inProgress =
+            policyMeta.getSnapshotInProgress();
         SnapshotLifecyclePolicy retrievedPolicy = policyMeta.getPolicy(); // <2>
         String id = retrievedPolicy.getId();
         String snapshotNameFormat = retrievedPolicy.getName();

+ 2 - 1
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/slm/SnapshotLifecyclePolicy.java

@@ -47,6 +47,8 @@ import static org.elasticsearch.cluster.metadata.MetaDataCreateIndexService.MAX_
 public class SnapshotLifecyclePolicy extends AbstractDiffable<SnapshotLifecyclePolicy>
     implements Writeable, Diffable<SnapshotLifecyclePolicy>, ToXContentObject {
 
+    public static final String POLICY_ID_METADATA_FIELD = "policy";
+
     private final String id;
     private final String name;
     private final String schedule;
@@ -59,7 +61,6 @@ public class SnapshotLifecyclePolicy extends AbstractDiffable<SnapshotLifecycleP
     private static final ParseField CONFIG = new ParseField("config");
     private static final IndexNameExpressionResolver.DateMathExpressionResolver DATE_MATH_RESOLVER =
         new IndexNameExpressionResolver.DateMathExpressionResolver();
-    private static final String POLICY_ID_METADATA_FIELD = "policy";
     private static final String METADATA_FIELD_NAME = "metadata";
 
     @SuppressWarnings("unchecked")

+ 106 - 3
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/slm/SnapshotLifecyclePolicyItem.java

@@ -6,13 +6,17 @@
 
 package org.elasticsearch.xpack.core.slm;
 
+import org.elasticsearch.cluster.SnapshotsInProgress;
 import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.ParseField;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.io.stream.Writeable;
 import org.elasticsearch.common.xcontent.ToXContentFragment;
+import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.snapshots.SnapshotId;
 
 import java.io.IOException;
 import java.util.Objects;
@@ -24,21 +28,27 @@ import java.util.Objects;
  */
 public class SnapshotLifecyclePolicyItem implements ToXContentFragment, Writeable {
 
+    private static final ParseField SNAPSHOT_IN_PROGRESS = new ParseField("in_progress");
+
     private final SnapshotLifecyclePolicy policy;
     private final long version;
     private final long modifiedDate;
+    @Nullable
+    private final SnapshotInProgress snapshotInProgress;
 
     @Nullable
     private final SnapshotInvocationRecord lastSuccess;
 
     @Nullable
     private final SnapshotInvocationRecord lastFailure;
-    public SnapshotLifecyclePolicyItem(SnapshotLifecyclePolicyMetadata policyMetadata) {
+    public SnapshotLifecyclePolicyItem(SnapshotLifecyclePolicyMetadata policyMetadata,
+                                       @Nullable SnapshotInProgress snapshotInProgress) {
         this.policy = policyMetadata.getPolicy();
         this.version = policyMetadata.getVersion();
         this.modifiedDate = policyMetadata.getModifiedDate();
         this.lastSuccess = policyMetadata.getLastSuccess();
         this.lastFailure = policyMetadata.getLastFailure();
+        this.snapshotInProgress = snapshotInProgress;
     }
 
     public SnapshotLifecyclePolicyItem(StreamInput in) throws IOException {
@@ -47,17 +57,20 @@ public class SnapshotLifecyclePolicyItem implements ToXContentFragment, Writeabl
         this.modifiedDate = in.readVLong();
         this.lastSuccess = in.readOptionalWriteable(SnapshotInvocationRecord::new);
         this.lastFailure = in.readOptionalWriteable(SnapshotInvocationRecord::new);
+        this.snapshotInProgress = in.readOptionalWriteable(SnapshotInProgress::new);
     }
 
     // For testing
 
     SnapshotLifecyclePolicyItem(SnapshotLifecyclePolicy policy, long version, long modifiedDate,
-                                SnapshotInvocationRecord lastSuccess, SnapshotInvocationRecord lastFailure) {
+                                SnapshotInvocationRecord lastSuccess, SnapshotInvocationRecord lastFailure,
+                                @Nullable SnapshotInProgress snapshotInProgress) {
         this.policy = policy;
         this.version = version;
         this.modifiedDate = modifiedDate;
         this.lastSuccess = lastSuccess;
         this.lastFailure = lastFailure;
+        this.snapshotInProgress = snapshotInProgress;
     }
     public SnapshotLifecyclePolicy getPolicy() {
         return policy;
@@ -79,6 +92,11 @@ public class SnapshotLifecyclePolicyItem implements ToXContentFragment, Writeabl
         return lastFailure;
     }
 
+    @Nullable
+    public SnapshotInProgress getSnapshotInProgress() {
+        return this.snapshotInProgress;
+    }
+
     @Override
     public void writeTo(StreamOutput out) throws IOException {
         policy.writeTo(out);
@@ -86,6 +104,7 @@ public class SnapshotLifecyclePolicyItem implements ToXContentFragment, Writeabl
         out.writeVLong(modifiedDate);
         out.writeOptionalWriteable(lastSuccess);
         out.writeOptionalWriteable(lastFailure);
+        out.writeOptionalWriteable(snapshotInProgress);
     }
 
     @Override
@@ -106,7 +125,8 @@ public class SnapshotLifecyclePolicyItem implements ToXContentFragment, Writeabl
             version == other.version &&
             modifiedDate == other.modifiedDate &&
             Objects.equals(lastSuccess, other.lastSuccess) &&
-            Objects.equals(lastFailure, other.lastFailure);
+            Objects.equals(lastFailure, other.lastFailure) &&
+            Objects.equals(snapshotInProgress, other.snapshotInProgress);
     }
 
     @Override
@@ -124,6 +144,9 @@ public class SnapshotLifecyclePolicyItem implements ToXContentFragment, Writeabl
         }
         builder.timeField(SnapshotLifecyclePolicyMetadata.NEXT_EXECUTION_MILLIS.getPreferredName(),
             SnapshotLifecyclePolicyMetadata.NEXT_EXECUTION.getPreferredName(), policy.calculateNextExecution());
+        if (snapshotInProgress != null) {
+            builder.field(SNAPSHOT_IN_PROGRESS.getPreferredName(), snapshotInProgress);
+        }
         builder.endObject();
         return builder;
     }
@@ -132,4 +155,84 @@ public class SnapshotLifecyclePolicyItem implements ToXContentFragment, Writeabl
     public String toString() {
         return Strings.toString(this);
     }
+
+    public static class SnapshotInProgress implements ToXContentObject, Writeable {
+        private static final ParseField NAME = new ParseField("name");
+        private static final ParseField UUID = new ParseField("uuid");
+        private static final ParseField STATE = new ParseField("state");
+        private static final ParseField START_TIME = new ParseField("start_time_millis");
+        private static final ParseField FAILURE = new ParseField("failure");
+
+        private final SnapshotId snapshotId;
+        private final SnapshotsInProgress.State state;
+        private final long startTime;
+        private final String failure;
+
+        public SnapshotInProgress(SnapshotId snapshotId, SnapshotsInProgress.State state, long startTime, @Nullable String failure) {
+            this.snapshotId = snapshotId;
+            this.state = state;
+            this.startTime = startTime;
+            this.failure = failure;
+        }
+
+        SnapshotInProgress(StreamInput in) throws IOException {
+            this.snapshotId = new SnapshotId(in);
+            this.state = in.readEnum(SnapshotsInProgress.State.class);
+            this.startTime = in.readVLong();
+            this.failure = in.readOptionalString();
+        }
+
+        public static SnapshotInProgress fromEntry(SnapshotsInProgress.Entry entry) {
+            return new SnapshotInProgress(entry.snapshot().getSnapshotId(),
+                entry.state(), entry.startTime(), entry.failure());
+        }
+
+        @Override
+        public void writeTo(StreamOutput out) throws IOException {
+            this.snapshotId.writeTo(out);
+            out.writeEnum(this.state);
+            out.writeVLong(this.startTime);
+            out.writeOptionalString(this.failure);
+        }
+
+        @Override
+        public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+            builder.startObject();
+            builder.field(NAME.getPreferredName(), snapshotId.getName());
+            builder.field(UUID.getPreferredName(), snapshotId.getUUID());
+            builder.field(STATE.getPreferredName(), state);
+            builder.timeField(START_TIME.getPreferredName(), "start_time", startTime);
+            if (failure != null) {
+                builder.field(FAILURE.getPreferredName(), failure);
+            }
+            builder.endObject();
+            return builder;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(snapshotId, state, startTime, failure);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == null) {
+                return false;
+            }
+
+            if (obj.getClass() != getClass()) {
+                return false;
+            }
+            SnapshotInProgress other = (SnapshotInProgress) obj;
+            return Objects.equals(snapshotId, other.snapshotId) &&
+                Objects.equals(state, other.state) &&
+                startTime == other.startTime &&
+                Objects.equals(failure, other.failure);
+        }
+
+        @Override
+        public String toString() {
+            return Strings.toString(this);
+        }
+    }
 }

+ 30 - 7
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/slm/SnapshotLifecyclePolicyItemTests.java

@@ -6,7 +6,9 @@
 
 package org.elasticsearch.xpack.core.slm;
 
+import org.elasticsearch.cluster.SnapshotsInProgress;
 import org.elasticsearch.common.io.stream.Writeable;
+import org.elasticsearch.snapshots.SnapshotId;
 import org.elasticsearch.test.AbstractWireSerializingTestCase;
 import org.elasticsearch.test.ESTestCase;
 
@@ -15,47 +17,68 @@ import static org.elasticsearch.xpack.core.slm.SnapshotLifecyclePolicyMetadataTe
 
 public class SnapshotLifecyclePolicyItemTests extends AbstractWireSerializingTestCase<SnapshotLifecyclePolicyItem> {
 
+    public static SnapshotLifecyclePolicyItem.SnapshotInProgress randomSnapshotInProgress() {
+        return rarely() ? null : new SnapshotLifecyclePolicyItem.SnapshotInProgress(
+            new SnapshotId("name-" + randomAlphaOfLength(3), "uuid-" + randomAlphaOfLength(3)),
+            randomFrom(SnapshotsInProgress.State.values()),
+            randomNonNegativeLong(),
+            randomBoolean() ? null : "failure!");
+    }
+
     @Override
     protected SnapshotLifecyclePolicyItem createTestInstance() {
-        return new SnapshotLifecyclePolicyItem(createRandomPolicyMetadata(randomAlphaOfLengthBetween(5, 10)));
+        return new SnapshotLifecyclePolicyItem(createRandomPolicyMetadata(randomAlphaOfLengthBetween(5, 10)), randomSnapshotInProgress());
     }
 
     @Override
     protected SnapshotLifecyclePolicyItem mutateInstance(SnapshotLifecyclePolicyItem instance) {
-        switch (between(0, 4)) {
+        switch (between(0, 5)) {
             case 0:
                 String newPolicyId = randomValueOtherThan(instance.getPolicy().getId(), () -> randomAlphaOfLengthBetween(5, 10));
                 return new SnapshotLifecyclePolicyItem(createRandomPolicy(newPolicyId),
                     instance.getVersion(),
                     instance.getModifiedDate(),
                     instance.getLastSuccess(),
-                    instance.getLastFailure());
+                    instance.getLastFailure(),
+                    instance.getSnapshotInProgress());
             case 1:
                 return new SnapshotLifecyclePolicyItem(instance.getPolicy(),
                     randomValueOtherThan(instance.getVersion(), ESTestCase::randomNonNegativeLong),
                     instance.getModifiedDate(),
                     instance.getLastSuccess(),
-                    instance.getLastFailure());
+                    instance.getLastFailure(),
+                    instance.getSnapshotInProgress());
             case 2:
                 return new SnapshotLifecyclePolicyItem(instance.getPolicy(),
                     instance.getVersion(),
                     randomValueOtherThan(instance.getModifiedDate(), ESTestCase::randomNonNegativeLong),
                     instance.getLastSuccess(),
-                    instance.getLastFailure());
+                    instance.getLastFailure(),
+                    instance.getSnapshotInProgress());
             case 3:
                 return new SnapshotLifecyclePolicyItem(instance.getPolicy(),
                     instance.getVersion(),
                     instance.getModifiedDate(),
                     randomValueOtherThan(instance.getLastSuccess(),
                         SnapshotInvocationRecordTests::randomSnapshotInvocationRecord),
-                    instance.getLastFailure());
+                    instance.getLastFailure(),
+                    instance.getSnapshotInProgress());
             case 4:
                 return new SnapshotLifecyclePolicyItem(instance.getPolicy(),
                     instance.getVersion(),
                     instance.getModifiedDate(),
                     instance.getLastSuccess(),
                     randomValueOtherThan(instance.getLastFailure(),
-                        SnapshotInvocationRecordTests::randomSnapshotInvocationRecord));
+                        SnapshotInvocationRecordTests::randomSnapshotInvocationRecord),
+                    instance.getSnapshotInProgress());
+            case 5:
+                return new SnapshotLifecyclePolicyItem(instance.getPolicy(),
+                    instance.getVersion(),
+                    instance.getModifiedDate(),
+                    instance.getLastSuccess(),
+                    instance.getLastFailure(),
+                    randomValueOtherThan(instance.getSnapshotInProgress(),
+                        SnapshotLifecyclePolicyItemTests::randomSnapshotInProgress));
             default:
                 throw new AssertionError("failure, got illegal switch case");
         }

+ 68 - 2
x-pack/plugin/ilm/qa/multi-node/src/test/java/org/elasticsearch/xpack/slm/SnapshotLifecycleIT.java

@@ -31,6 +31,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
 import static org.hamcrest.Matchers.containsString;
@@ -171,7 +172,6 @@ public class SnapshotLifecycleIT extends ESRestTestCase {
         final String policyName = "test-policy";
         final String repoId = "my-repo";
         int docCount = randomIntBetween(10, 50);
-        List<IndexRequestBuilder> indexReqs = new ArrayList<>();
         for (int i = 0; i < docCount; i++) {
             index(client(), indexName, "" + i, "foo", "bar");
         }
@@ -221,6 +221,68 @@ public class SnapshotLifecycleIT extends ESRestTestCase {
         });
     }
 
+    @SuppressWarnings("unchecked")
+    public void testSnapshotInProgress() throws Exception {
+        final String indexName = "test";
+        final String policyName = "test-policy";
+        final String repoId = "my-repo";
+        int docCount = 20;
+        for (int i = 0; i < docCount; i++) {
+            index(client(), indexName, "" + i, "foo", "bar");
+        }
+
+        // Create a snapshot repo
+        inializeRepo(repoId, 1);
+
+        createSnapshotPolicy(policyName, "snap", "1 2 3 4 5 ?", repoId, indexName, true);
+
+        Response executeRepsonse = client().performRequest(new Request("PUT", "/_slm/policy/" + policyName + "/_execute"));
+
+        try (XContentParser parser = JsonXContent.jsonXContent.createParser(NamedXContentRegistry.EMPTY,
+            DeprecationHandler.THROW_UNSUPPORTED_OPERATION, EntityUtils.toByteArray(executeRepsonse.getEntity()))) {
+            final String snapshotName = parser.mapStrings().get("snapshot_name");
+
+            // Check that the executed snapshot shows up in the SLM output
+            assertBusy(() -> {
+                try {
+                    Response response = client().performRequest(new Request("GET", "/_slm/policy" + (randomBoolean() ? "" : "?human")));
+                    Map<String, Object> policyResponseMap;
+                    try (InputStream content = response.getEntity().getContent()) {
+                        policyResponseMap = XContentHelper.convertToMap(XContentType.JSON.xContent(), content, true);
+                    }
+                    assertThat(policyResponseMap.size(), greaterThan(0));
+                    Optional<Map<String, Object>> inProgress = Optional.ofNullable((Map<String, Object>) policyResponseMap.get(policyName))
+                        .map(policy -> (Map<String, Object>) policy.get("in_progress"));
+
+                    if (inProgress.isPresent()) {
+                        Map<String, Object> inProgressMap = inProgress.get();
+                        assertThat(inProgressMap.get("name"), equalTo(snapshotName));
+                        assertNotNull(inProgressMap.get("uuid"));
+                        assertThat(inProgressMap.get("state"), equalTo("STARTED"));
+                        assertThat((long) inProgressMap.get("start_time_millis"), greaterThan(0L));
+                        assertNull(inProgressMap.get("failure"));
+                    } else {
+                        fail("expected in_progress to contain a running snapshot, but the response was " + policyResponseMap);
+                    }
+                } catch (ResponseException e) {
+                    fail("expected policy to exist but it does not: " + EntityUtils.toString(e.getResponse().getEntity()));
+                }
+            });
+
+            // Cancel the snapshot since it is not going to complete quickly
+            assertOK(client().performRequest(new Request("DELETE", "/_snapshot/" + repoId + "/" + snapshotName)));
+        }
+
+        Request delReq = new Request("DELETE", "/_slm/policy/" + policyName);
+        assertOK(client().performRequest(delReq));
+
+        // It's possible there could have been a snapshot in progress when the
+        // policy is deleted, so wait for it to be finished
+        assertBusy(() -> {
+            assertThat(wipeSnapshots().size(), equalTo(0));
+        });
+    }
+
     @SuppressWarnings("unchecked")
     private static Map<String, Object> extractMetadata(Map<String, Object> snapshotResponseMap, String snapshotPrefix) {
         List<Map<String, Object>> snapResponse = ((List<Map<String, Object>>) snapshotResponseMap.get("responses")).stream()
@@ -304,6 +366,10 @@ public class SnapshotLifecycleIT extends ESRestTestCase {
     }
 
     private void inializeRepo(String repoName) throws IOException {
+        inializeRepo(repoName, 256);
+    }
+
+    private void inializeRepo(String repoName, int maxBytesPerSecond) throws IOException {
         Request request = new Request("PUT", "/_snapshot/" + repoName);
         request.setJsonEntity(Strings
             .toString(JsonXContent.contentBuilder()
@@ -312,7 +378,7 @@ public class SnapshotLifecycleIT extends ESRestTestCase {
                 .startObject("settings")
                 .field("compress", randomBoolean())
                 .field("location", System.getProperty("tests.path.repo"))
-                .field("max_snapshot_bytes_per_sec", "256b")
+                .field("max_snapshot_bytes_per_sec", maxBytesPerSecond + "b")
                 .endObject()
                 .endObject()));
         assertOK(client().performRequest(request));

+ 25 - 4
x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/action/TransportGetSnapshotLifecycleAction.java

@@ -12,6 +12,7 @@ import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.action.support.ActionFilters;
 import org.elasticsearch.action.support.master.TransportMasterNodeAction;
 import org.elasticsearch.cluster.ClusterState;
+import org.elasticsearch.cluster.SnapshotsInProgress;
 import org.elasticsearch.cluster.block.ClusterBlockException;
 import org.elasticsearch.cluster.block.ClusterBlockLevel;
 import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
@@ -22,14 +23,17 @@ import org.elasticsearch.tasks.Task;
 import org.elasticsearch.threadpool.ThreadPool;
 import org.elasticsearch.transport.TransportService;
 import org.elasticsearch.xpack.core.slm.SnapshotLifecycleMetadata;
+import org.elasticsearch.xpack.core.slm.SnapshotLifecyclePolicy;
 import org.elasticsearch.xpack.core.slm.SnapshotLifecyclePolicyItem;
 import org.elasticsearch.xpack.core.slm.action.GetSnapshotLifecycleAction;
 
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -62,10 +66,27 @@ public class TransportGetSnapshotLifecycleAction extends
         if (snapMeta == null) {
             listener.onResponse(new GetSnapshotLifecycleAction.Response(Collections.emptyList()));
         } else {
+            final Map<String, SnapshotLifecyclePolicyItem.SnapshotInProgress> inProgress;
+            SnapshotsInProgress sip = state.custom(SnapshotsInProgress.TYPE);
+            if (sip == null) {
+                inProgress = Collections.emptyMap();
+            } else {
+                inProgress = new HashMap<>();
+                for (SnapshotsInProgress.Entry entry : sip.entries()) {
+                    Map<String, Object> meta = entry.userMetadata();
+                    if (meta == null ||
+                        meta.get(SnapshotLifecyclePolicy.POLICY_ID_METADATA_FIELD) == null ||
+                        (meta.get(SnapshotLifecyclePolicy.POLICY_ID_METADATA_FIELD) instanceof String == false)) {
+                        continue;
+                    }
+
+                    String policyId = (String) meta.get(SnapshotLifecyclePolicy.POLICY_ID_METADATA_FIELD);
+                    inProgress.put(policyId, SnapshotLifecyclePolicyItem.SnapshotInProgress.fromEntry(entry));
+                }
+            }
+
             final Set<String> ids = new HashSet<>(Arrays.asList(request.getLifecycleIds()));
-            List<SnapshotLifecyclePolicyItem> lifecycles = snapMeta.getSnapshotConfigurations()
-                .values()
-                .stream()
+            List<SnapshotLifecyclePolicyItem> lifecycles = snapMeta.getSnapshotConfigurations().values().stream()
                 .filter(meta -> {
                     if (ids.isEmpty()) {
                         return true;
@@ -73,7 +94,7 @@ public class TransportGetSnapshotLifecycleAction extends
                         return ids.contains(meta.getPolicy().getId());
                     }
                 })
-                .map(SnapshotLifecyclePolicyItem::new)
+                .map(policyMeta -> new SnapshotLifecyclePolicyItem(policyMeta, inProgress.get(policyMeta.getPolicy().getId())))
                 .collect(Collectors.toList());
             listener.onResponse(new GetSnapshotLifecycleAction.Response(lifecycles));
         }