瀏覽代碼

Add usage to get ILM policy response (#74518)

This commit adds the "in_use_by" object to the response for ILM policies. This map shows the
indices, data streams, and composable templates that use the ILM policy.

An example output may look like:

```json
{
  "logs" : {
    "version" : 1,
    "modified_date" : "2021-06-23T18:42:08.381Z",
    "policy" : {
      ...
    },
    "in_use_by" : {
      "indices" : [".ds-logs-foo-barbaz-2021.06.23-000001", ".ds-logs-foo-other-2021.06.23-000001"],
      "data_streams" : ["logs-foo-barbaz", "logs-foo-other"],
      "composable_templates" : ["logs"]
    }
  }
}
```

Resolves #73869
Lee Hinman 4 年之前
父節點
當前提交
997db17852

+ 6 - 0
docs/reference/ilm/apis/get-lifecycle.asciidoc

@@ -107,6 +107,11 @@ If the request succeeds, the body of the response contains the policy definition
           }
         }
       }
+    },
+    "in_use_by" : { <3>
+      "indices" : [],
+      "data_streams" : [],
+      "composable_templates" : []
     }
   }
 }
@@ -115,3 +120,4 @@ If the request succeeds, the body of the response contains the policy definition
 
 <1> The policy version is incremented whenever the policy is updated
 <2> When this policy was last modified
+<3> Which indices, data streams, or templates currently use this policy

+ 122 - 0
server/src/main/java/org/elasticsearch/cluster/metadata/ItemUsage.java

@@ -0,0 +1,122 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.cluster.metadata;
+
+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.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.core.Nullable;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A class encapsulating the usage of a particular "thing" by something else
+ */
+public class ItemUsage implements Writeable, ToXContentObject {
+
+    private final Set<String> indices;
+    private final Set<String> dataStreams;
+    private final Set<String> composableTemplates;
+
+    /**
+     * Create a new usage, a {@code null} value indicates that the item *cannot* be used by the
+     * thing, otherwise use an empty collection to indicate no usage.
+     */
+    public ItemUsage(@Nullable Collection<String> indices,
+                     @Nullable Collection<String> dataStreams,
+                     @Nullable Collection<String> composableTemplates) {
+        this.indices = indices == null ? null : new HashSet<>(indices);
+        this.dataStreams = dataStreams == null ? null : new HashSet<>(dataStreams);
+        this.composableTemplates = composableTemplates == null ? null : new HashSet<>(composableTemplates);
+    }
+
+    public ItemUsage(StreamInput in) throws IOException {
+        if (in.readBoolean()) {
+            this.indices = in.readSet(StreamInput::readString);
+        } else {
+            this.indices = null;
+        }
+        if (in.readBoolean()) {
+            this.dataStreams = in.readSet(StreamInput::readString);
+        } else {
+            this.dataStreams = null;
+        }
+        if (in.readBoolean()) {
+            this.composableTemplates = in.readSet(StreamInput::readString);
+        } else {
+            this.composableTemplates = null;
+        }
+    }
+
+    public Set<String> getIndices() {
+        return indices;
+    }
+
+    public Set<String> getDataStreams() {
+        return dataStreams;
+    }
+
+    public Set<String> getComposableTemplates() {
+        return composableTemplates;
+    }
+
+    @Override
+    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+        builder.startObject();
+        if (this.indices != null) {
+            builder.field("indices", this.indices);
+        }
+        if (this.dataStreams != null) {
+            builder.field("data_streams", this.dataStreams);
+        }
+        if (this.composableTemplates != null) {
+            builder.field("composable_templates", this.composableTemplates);
+        }
+        builder.endObject();
+        return builder;
+    }
+
+    @Override
+    public void writeTo(StreamOutput out) throws IOException {
+        out.writeOptionalStringCollection(this.indices);
+        out.writeOptionalStringCollection(this.dataStreams);
+        out.writeOptionalStringCollection(this.composableTemplates);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(this.indices, this.dataStreams, this.composableTemplates);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        ItemUsage other = (ItemUsage) obj;
+        return Objects.equals(indices, other.indices) &&
+            Objects.equals(dataStreams, other.dataStreams) &&
+            Objects.equals(composableTemplates, other.composableTemplates);
+    }
+
+    @Override
+    public String toString() {
+        return Strings.toString(this);
+    }
+}

+ 47 - 0
server/src/test/java/org/elasticsearch/cluster/metadata/ItemUsageTests.java

@@ -0,0 +1,47 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.cluster.metadata;
+
+import org.elasticsearch.Version;
+import org.elasticsearch.core.Nullable;
+import org.elasticsearch.test.AbstractWireTestCase;
+
+import java.io.IOException;
+import java.util.List;
+
+public class ItemUsageTests extends AbstractWireTestCase<ItemUsage> {
+
+    public static ItemUsage randomUsage() {
+        return new ItemUsage(randomStringList(), randomStringList(), randomStringList());
+    }
+
+    @Nullable
+    private static List<String> randomStringList() {
+        if (randomBoolean()) {
+            return null;
+        } else {
+            return randomList(0, 1, () -> randomAlphaOfLengthBetween(2, 10));
+        }
+    }
+
+    @Override
+    protected ItemUsage createTestInstance() {
+        return randomUsage();
+    }
+
+    @Override
+    protected ItemUsage copyInstance(ItemUsage instance, Version version) throws IOException {
+        return new ItemUsage(instance.getIndices(), instance.getDataStreams(), instance.getComposableTemplates());
+    }
+
+    @Override
+    protected ItemUsage mutateInstance(ItemUsage instance) throws IOException {
+        return super.mutateInstance(instance);
+    }
+}

+ 47 - 1
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/LifecyclePolicyUtils.java

@@ -8,9 +8,15 @@
 package org.elasticsearch.xpack.core.ilm;
 
 import org.elasticsearch.ElasticsearchParseException;
+import org.elasticsearch.action.support.IndicesOptions;
+import org.elasticsearch.cluster.ClusterState;
+import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
+import org.elasticsearch.cluster.metadata.ItemUsage;
+import org.elasticsearch.cluster.metadata.MetadataIndexTemplateService;
 import org.elasticsearch.common.bytes.BytesArray;
 import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.compress.NotXContentException;
+import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
 import org.elasticsearch.common.xcontent.NamedXContentRegistry;
 import org.elasticsearch.common.xcontent.XContentHelper;
@@ -21,9 +27,12 @@ import org.elasticsearch.core.internal.io.Streams;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
 
 /**
- * A utility class used for loading index lifecycle policies from the resource classpath
+ * A utility class used for index lifecycle policies
  */
 public class LifecyclePolicyUtils {
 
@@ -76,4 +85,41 @@ public class LifecyclePolicyUtils {
             throw new ElasticsearchParseException("invalid policy", e);
         }
     }
+
+    /**
+     * Given a cluster state and ILM policy, calculate the {@link ItemUsage} of
+     * the policy (what indices, data streams, and templates use the policy)
+     */
+    public static ItemUsage calculateUsage(final IndexNameExpressionResolver indexNameExpressionResolver,
+                                           final ClusterState state, final String policyName) {
+        final List<String> indices = StreamSupport.stream(state.metadata().indices().values().spliterator(), false)
+            .map(cursor -> cursor.value)
+            .filter(indexMetadata -> policyName.equals(LifecycleSettings.LIFECYCLE_NAME_SETTING.get(indexMetadata.getSettings())))
+            .map(indexMetadata -> indexMetadata.getIndex().getName())
+            .collect(Collectors.toList());
+
+        final List<String> allDataStreams = indexNameExpressionResolver.dataStreamNames(state,
+            IndicesOptions.LENIENT_EXPAND_OPEN_CLOSED_HIDDEN);
+
+        final List<String> dataStreams = allDataStreams.stream()
+            .filter(dsName -> {
+                String indexTemplate = MetadataIndexTemplateService.findV2Template(state.metadata(), dsName, false);
+                if (indexTemplate != null) {
+                    Settings settings = MetadataIndexTemplateService.resolveSettings(state.metadata(), indexTemplate);
+                    return policyName.equals(LifecycleSettings.LIFECYCLE_NAME_SETTING.get(settings));
+                } else {
+                    return false;
+                }
+            })
+            .collect(Collectors.toList());
+
+        final List<String> composableTemplates = state.metadata().templatesV2().keySet().stream()
+            .filter(templateName -> {
+                Settings settings = MetadataIndexTemplateService.resolveSettings(state.metadata(), templateName);
+                return policyName.equals(LifecycleSettings.LIFECYCLE_NAME_SETTING.get(settings));
+            })
+            .collect(Collectors.toList());
+
+        return new ItemUsage(indices, dataStreams, composableTemplates);
+    }
 }

+ 22 - 4
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/action/GetLifecycleAction.java

@@ -7,10 +7,12 @@
 
 package org.elasticsearch.xpack.core.ilm.action;
 
+import org.elasticsearch.Version;
 import org.elasticsearch.action.ActionRequestValidationException;
 import org.elasticsearch.action.ActionResponse;
 import org.elasticsearch.action.ActionType;
 import org.elasticsearch.action.support.master.AcknowledgedRequest;
+import org.elasticsearch.cluster.metadata.ItemUsage;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
@@ -21,6 +23,7 @@ import org.elasticsearch.xpack.core.ilm.LifecyclePolicy;
 
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
@@ -57,6 +60,7 @@ public class GetLifecycleAction extends ActionType<GetLifecycleAction.Response>
                 builder.field("version", item.getVersion());
                 builder.field("modified_date", item.getModifiedDate());
                 builder.field("policy", item.getLifecyclePolicy());
+                builder.field("in_use_by", item.getUsage());
                 builder.endObject();
             }
             builder.endObject();
@@ -149,17 +153,24 @@ public class GetLifecycleAction extends ActionType<GetLifecycleAction.Response>
         private final LifecyclePolicy lifecyclePolicy;
         private final long version;
         private final String modifiedDate;
+        private final ItemUsage usage;
 
-        public LifecyclePolicyResponseItem(LifecyclePolicy lifecyclePolicy, long version, String modifiedDate) {
+        public LifecyclePolicyResponseItem(LifecyclePolicy lifecyclePolicy, long version, String modifiedDate, ItemUsage usage) {
             this.lifecyclePolicy = lifecyclePolicy;
             this.version = version;
             this.modifiedDate = modifiedDate;
+            this.usage = usage;
         }
 
         LifecyclePolicyResponseItem(StreamInput in) throws IOException {
             this.lifecyclePolicy = new LifecyclePolicy(in);
             this.version = in.readVLong();
             this.modifiedDate = in.readString();
+            if (in.getVersion().onOrAfter(Version.V_8_0_0)) {
+                this.usage = new ItemUsage(in);
+            } else {
+                this.usage = new ItemUsage(Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
+            }
         }
 
         @Override
@@ -167,6 +178,9 @@ public class GetLifecycleAction extends ActionType<GetLifecycleAction.Response>
             lifecyclePolicy.writeTo(out);
             out.writeVLong(version);
             out.writeString(modifiedDate);
+            if (out.getVersion().onOrAfter(Version.V_8_0_0)) {
+                this.usage.writeTo(out);
+            }
         }
 
         public LifecyclePolicy getLifecyclePolicy() {
@@ -181,9 +195,13 @@ public class GetLifecycleAction extends ActionType<GetLifecycleAction.Response>
             return modifiedDate;
         }
 
+        public ItemUsage getUsage() {
+            return usage;
+        }
+
         @Override
         public int hashCode() {
-            return Objects.hash(lifecyclePolicy, version, modifiedDate);
+            return Objects.hash(lifecyclePolicy, version, modifiedDate, usage);
         }
 
         @Override
@@ -197,9 +215,9 @@ public class GetLifecycleAction extends ActionType<GetLifecycleAction.Response>
             LifecyclePolicyResponseItem other = (LifecyclePolicyResponseItem) obj;
             return Objects.equals(lifecyclePolicy, other.lifecyclePolicy) &&
                 Objects.equals(version, other.version) &&
-                Objects.equals(modifiedDate, other.modifiedDate);
+                Objects.equals(modifiedDate, other.modifiedDate) &&
+                Objects.equals(usage, other.usage);
         }
-
     }
 
 }

+ 4 - 0
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/LifecyclePolicyMetadataTests.java

@@ -89,6 +89,10 @@ public class LifecyclePolicyMetadataTests extends AbstractSerializingTestCase<Li
 
     @Override
     protected LifecyclePolicyMetadata createTestInstance() {
+        return createRandomPolicyMetadata(lifecycleName);
+    }
+
+    public static LifecyclePolicyMetadata createRandomPolicyMetadata(String lifecycleName) {
         Map<String, String> headers = new HashMap<>();
         int numberHeaders = between(0, 10);
         for (int i = 0; i < numberHeaders; i++) {

+ 145 - 0
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/LifecyclePolicyUtilsTests.java

@@ -0,0 +1,145 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.core.ilm;
+
+import org.elasticsearch.Version;
+import org.elasticsearch.cluster.ClusterName;
+import org.elasticsearch.cluster.ClusterState;
+import org.elasticsearch.cluster.metadata.ComposableIndexTemplate;
+import org.elasticsearch.cluster.metadata.ComposableIndexTemplateMetadata;
+import org.elasticsearch.cluster.metadata.DataStream;
+import org.elasticsearch.cluster.metadata.IndexMetadata;
+import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
+import org.elasticsearch.cluster.metadata.ItemUsage;
+import org.elasticsearch.cluster.metadata.Metadata;
+import org.elasticsearch.cluster.metadata.Template;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.util.concurrent.ThreadContext;
+import org.elasticsearch.index.Index;
+import org.elasticsearch.indices.EmptySystemIndices;
+import org.elasticsearch.test.ESTestCase;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class LifecyclePolicyUtilsTests extends ESTestCase {
+    public void testCalculateUsage() {
+        final IndexNameExpressionResolver iner =
+            new IndexNameExpressionResolver(new ThreadContext(Settings.EMPTY), EmptySystemIndices.INSTANCE);
+
+        {
+            // Test where policy does not exist
+            ClusterState state = ClusterState.builder(new ClusterName("mycluster")).build();
+            assertThat(LifecyclePolicyUtils.calculateUsage(iner, state, "mypolicy"),
+                equalTo(new ItemUsage(Collections.emptyList(), Collections.emptyList(), Collections.emptyList())));
+        }
+
+        {
+            // Test where policy is not used by anything
+            ClusterState state = ClusterState.builder(new ClusterName("mycluster"))
+                .metadata(Metadata.builder()
+                    .putCustom(IndexLifecycleMetadata.TYPE,
+                        new IndexLifecycleMetadata(Collections.singletonMap("mypolicy",
+                            LifecyclePolicyMetadataTests.createRandomPolicyMetadata("mypolicy")), OperationMode.RUNNING))
+                    .build())
+                .build();
+            assertThat(LifecyclePolicyUtils.calculateUsage(iner, state, "mypolicy"),
+                equalTo(new ItemUsage(Collections.emptyList(), Collections.emptyList(), Collections.emptyList())));
+        }
+
+        {
+            // Test where policy exists and is used by an index
+            ClusterState state = ClusterState.builder(new ClusterName("mycluster"))
+                .metadata(Metadata.builder()
+                    .putCustom(IndexLifecycleMetadata.TYPE,
+                        new IndexLifecycleMetadata(Collections.singletonMap("mypolicy",
+                            LifecyclePolicyMetadataTests.createRandomPolicyMetadata("mypolicy")), OperationMode.RUNNING))
+                    .put(IndexMetadata.builder("myindex")
+                    .settings(Settings.builder()
+                        .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1)
+                        .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0)
+                        .put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT)
+                        .put(LifecycleSettings.LIFECYCLE_NAME, "mypolicy")
+                        .build()))
+                    .build())
+                .build();
+            assertThat(LifecyclePolicyUtils.calculateUsage(iner, state, "mypolicy"),
+                equalTo(new ItemUsage(Collections.singleton("myindex"), Collections.emptyList(), Collections.emptyList())));
+        }
+
+        {
+            // Test where policy exists and is used by an index, and template
+            ClusterState state = ClusterState.builder(new ClusterName("mycluster"))
+                .metadata(Metadata.builder()
+                    .putCustom(IndexLifecycleMetadata.TYPE,
+                        new IndexLifecycleMetadata(Collections.singletonMap("mypolicy",
+                            LifecyclePolicyMetadataTests.createRandomPolicyMetadata("mypolicy")), OperationMode.RUNNING))
+                    .put(IndexMetadata.builder("myindex")
+                        .settings(Settings.builder()
+                            .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1)
+                            .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0)
+                            .put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT)
+                            .put(LifecycleSettings.LIFECYCLE_NAME, "mypolicy")
+                            .build()))
+                    .putCustom(ComposableIndexTemplateMetadata.TYPE,
+                        new ComposableIndexTemplateMetadata(Collections.singletonMap("mytemplate",
+                            new ComposableIndexTemplate(Collections.singletonList("myds"),
+                                new Template(Settings.builder().put(LifecycleSettings.LIFECYCLE_NAME, "mypolicy").build(), null, null),
+                                null, null, null, null, new ComposableIndexTemplate.DataStreamTemplate(false)))))
+                    .build())
+                .build();
+            assertThat(LifecyclePolicyUtils.calculateUsage(iner, state, "mypolicy"),
+                equalTo(new ItemUsage(Collections.singleton("myindex"), Collections.emptyList(), Collections.singleton("mytemplate"))));
+        }
+
+        {
+            // Test where policy exists and is used by an index, datastream, and template
+            ClusterState state = ClusterState.builder(new ClusterName("mycluster"))
+                .metadata(Metadata.builder()
+                    .putCustom(IndexLifecycleMetadata.TYPE,
+                        new IndexLifecycleMetadata(Collections.singletonMap("mypolicy",
+                            LifecyclePolicyMetadataTests.createRandomPolicyMetadata("mypolicy")), OperationMode.RUNNING))
+                    .put(IndexMetadata.builder("myindex")
+                        .settings(Settings.builder()
+                            .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1)
+                            .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0)
+                            .put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT)
+                            .put(LifecycleSettings.LIFECYCLE_NAME, "mypolicy")
+                            .build()))
+                    .put(IndexMetadata.builder("another")
+                        .settings(Settings.builder()
+                            .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1)
+                            .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0)
+                            .put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT)
+                            .put(LifecycleSettings.LIFECYCLE_NAME, "mypolicy")
+                            .build()))
+                    .put(IndexMetadata.builder("other")
+                        .settings(Settings.builder()
+                            .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1)
+                            .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0)
+                            .put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT)
+                            .put(LifecycleSettings.LIFECYCLE_NAME, "otherpolicy")
+                            .build()))
+                    .put(new DataStream("myds", new DataStream.TimestampField("@timestamp"),
+                        Collections.singletonList(new Index("myindex", "uuid"))))
+                    .putCustom(ComposableIndexTemplateMetadata.TYPE,
+                        new ComposableIndexTemplateMetadata(Collections.singletonMap("mytemplate",
+                            new ComposableIndexTemplate(Collections.singletonList("myds"),
+                                new Template(Settings.builder().put(LifecycleSettings.LIFECYCLE_NAME, "mypolicy").build(), null, null),
+                                null, null, null, null, new ComposableIndexTemplate.DataStreamTemplate(false)))))
+                    .build())
+                .build();
+            assertThat(LifecyclePolicyUtils.calculateUsage(iner, state, "mypolicy"),
+                equalTo(new ItemUsage(Arrays.asList("myindex", "another"),
+                    Collections.singleton("myds"),
+                    Collections.singleton("mytemplate"))));
+        }
+    }
+}

+ 18 - 3
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/action/GetLifecycleResponseTests.java

@@ -6,8 +6,10 @@
  */
 package org.elasticsearch.xpack.core.ilm.action;
 
+import org.elasticsearch.cluster.metadata.ItemUsage;
 import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
 import org.elasticsearch.common.io.stream.Writeable;
+import org.elasticsearch.core.Nullable;
 import org.elasticsearch.test.AbstractWireSerializingTestCase;
 import org.elasticsearch.xpack.core.ilm.LifecycleAction;
 import org.elasticsearch.xpack.core.ilm.LifecycleType;
@@ -30,7 +32,7 @@ public class GetLifecycleResponseTests extends AbstractWireSerializingTestCase<R
         List<LifecyclePolicyResponseItem> responseItems = new ArrayList<>();
         for (int i = 0; i < randomIntBetween(0, 2); i++) {
             responseItems.add(new LifecyclePolicyResponseItem(randomTestLifecyclePolicy(randomPrefix + i),
-                randomNonNegativeLong(), randomAlphaOfLength(8)));
+                randomNonNegativeLong(), randomAlphaOfLength(8), randomUsage()));
         }
         return new Response(responseItems);
     }
@@ -52,14 +54,27 @@ public class GetLifecycleResponseTests extends AbstractWireSerializingTestCase<R
         if (responseItems.size() > 0) {
             if (randomBoolean()) {
                 responseItems.add(new LifecyclePolicyResponseItem(randomTestLifecyclePolicy(randomAlphaOfLength(5)),
-                    randomNonNegativeLong(), randomAlphaOfLength(4)));
+                    randomNonNegativeLong(), randomAlphaOfLength(4), randomUsage()));
             } else {
                 responseItems.remove(0);
             }
         } else {
             responseItems.add(new LifecyclePolicyResponseItem(randomTestLifecyclePolicy(randomAlphaOfLength(2)),
-                randomNonNegativeLong(), randomAlphaOfLength(4)));
+                randomNonNegativeLong(), randomAlphaOfLength(4), randomUsage()));
         }
         return new Response(responseItems);
     }
+
+    public static ItemUsage randomUsage() {
+        return new ItemUsage(randomStringList(), randomStringList(), randomStringList());
+    }
+
+    @Nullable
+    private static List<String> randomStringList() {
+        if (randomBoolean()) {
+            return null;
+        } else {
+            return randomList(0, 1, () -> randomAlphaOfLengthBetween(2, 10));
+        }
+    }
 }

+ 11 - 3
x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/action/TransportGetLifecycleAction.java

@@ -15,6 +15,7 @@ import org.elasticsearch.cluster.ClusterState;
 import org.elasticsearch.cluster.block.ClusterBlockException;
 import org.elasticsearch.cluster.block.ClusterBlockLevel;
 import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
+import org.elasticsearch.cluster.metadata.MetadataIndexTemplateService;
 import org.elasticsearch.cluster.service.ClusterService;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.tasks.Task;
@@ -22,6 +23,7 @@ import org.elasticsearch.threadpool.ThreadPool;
 import org.elasticsearch.transport.TransportService;
 import org.elasticsearch.xpack.core.ilm.IndexLifecycleMetadata;
 import org.elasticsearch.xpack.core.ilm.LifecyclePolicyMetadata;
+import org.elasticsearch.xpack.core.ilm.LifecyclePolicyUtils;
 import org.elasticsearch.xpack.core.ilm.action.GetLifecycleAction;
 import org.elasticsearch.xpack.core.ilm.action.GetLifecycleAction.LifecyclePolicyResponseItem;
 import org.elasticsearch.xpack.core.ilm.action.GetLifecycleAction.Request;
@@ -34,11 +36,15 @@ import java.util.List;
 
 public class TransportGetLifecycleAction extends TransportMasterNodeAction<Request, Response> {
 
+    private final MetadataIndexTemplateService templateService;
+
     @Inject
     public TransportGetLifecycleAction(TransportService transportService, ClusterService clusterService, ThreadPool threadPool,
-                                       ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver) {
+                                       ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver,
+                                       MetadataIndexTemplateService metadataIndexTemplateService) {
         super(GetLifecycleAction.NAME, transportService, clusterService, threadPool, actionFilters, Request::new,
             indexNameExpressionResolver, Response::new, ThreadPool.Names.SAME);
+        this.templateService = metadataIndexTemplateService;
     }
 
     @Override
@@ -58,7 +64,8 @@ public class TransportGetLifecycleAction extends TransportMasterNodeAction<Reque
                 requestedPolicies = new ArrayList<>(metadata.getPolicyMetadatas().size());
                 for (LifecyclePolicyMetadata policyMetadata : metadata.getPolicyMetadatas().values()) {
                     requestedPolicies.add(new LifecyclePolicyResponseItem(policyMetadata.getPolicy(),
-                        policyMetadata.getVersion(), policyMetadata.getModifiedDateString()));
+                        policyMetadata.getVersion(), policyMetadata.getModifiedDateString(),
+                        LifecyclePolicyUtils.calculateUsage(indexNameExpressionResolver, state, policyMetadata.getName())));
                 }
             } else {
                 requestedPolicies = new ArrayList<>(request.getPolicyNames().length);
@@ -69,7 +76,8 @@ public class TransportGetLifecycleAction extends TransportMasterNodeAction<Reque
                         return;
                     }
                     requestedPolicies.add(new LifecyclePolicyResponseItem(policyMetadata.getPolicy(),
-                        policyMetadata.getVersion(), policyMetadata.getModifiedDateString()));
+                        policyMetadata.getVersion(), policyMetadata.getModifiedDateString(),
+                        LifecyclePolicyUtils.calculateUsage(indexNameExpressionResolver, state, policyMetadata.getName())));
                 }
             }
             listener.onResponse(new Response(requestedPolicies));