浏览代码

[HealthAPI] Add size parameter that controls the number of affected resources returned (#92399)

This adds a `size` parameter that controls the maximum number of
returned affected resources. The parameter defaults to `1000`, must be
positive, and less than `10_000`
Andrei Dan 2 年之前
父节点
当前提交
3723af3ccd
共有 26 个文件被更改,包括 391 次插入102 次删除
  1. 7 0
      docs/changelog/92399.yaml
  2. 6 0
      docs/reference/health/health.asciidoc
  3. 1 1
      qa/smoke-test-http/src/javaRestTest/java/org/elasticsearch/http/HealthRestCancellationIT.java
  4. 12 7
      rest-api-spec/src/main/resources/rest-api-spec/api/_internal.health.json
  5. 2 1
      server/src/internalClusterTest/java/org/elasticsearch/discovery/StableMasterDisruptionIT.java
  6. 7 5
      server/src/internalClusterTest/java/org/elasticsearch/health/GetHealthActionIT.java
  7. 2 2
      server/src/internalClusterTest/java/org/elasticsearch/health/HealthServiceIT.java
  8. 1 1
      server/src/internalClusterTest/java/org/elasticsearch/health/node/DiskHealthIndicatorServiceIT.java
  9. 1 1
      server/src/internalClusterTest/java/org/elasticsearch/snapshots/RepositoryIntegrityHealthIndicatorServiceIT.java
  10. 1 1
      server/src/main/java/org/elasticsearch/cluster/coordination/StableMasterHealthIndicatorService.java
  11. 5 3
      server/src/main/java/org/elasticsearch/cluster/routing/allocation/ShardsAvailabilityHealthIndicatorService.java
  12. 27 11
      server/src/main/java/org/elasticsearch/health/GetHealthAction.java
  13. 5 1
      server/src/main/java/org/elasticsearch/health/HealthIndicatorService.java
  14. 12 7
      server/src/main/java/org/elasticsearch/health/HealthService.java
  15. 4 1
      server/src/main/java/org/elasticsearch/health/RestGetHealthAction.java
  16. 12 11
      server/src/main/java/org/elasticsearch/health/node/DiskHealthIndicatorService.java
  17. 7 2
      server/src/main/java/org/elasticsearch/snapshots/RepositoryIntegrityHealthIndicatorService.java
  18. 106 36
      server/src/test/java/org/elasticsearch/cluster/routing/allocation/ShardsAvailabilityHealthIndicatorServiceTests.java
  19. 27 0
      server/src/test/java/org/elasticsearch/health/GetHealthRequestTests.java
  20. 1 1
      server/src/test/java/org/elasticsearch/health/HealthIndicatorServiceTests.java
  21. 18 4
      server/src/test/java/org/elasticsearch/health/HealthServiceTests.java
  22. 81 0
      server/src/test/java/org/elasticsearch/health/node/DiskHealthIndicatorServiceTests.java
  23. 37 0
      server/src/test/java/org/elasticsearch/snapshots/RepositoryIntegrityHealthIndicatorServiceTests.java
  24. 3 3
      x-pack/plugin/core/src/internalClusterTest/java/org/elasticsearch/xpack/cluster/routing/allocation/DataTierShardAvailabilityHealthIndicatorIT.java
  25. 1 1
      x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/IlmHealthIndicatorService.java
  26. 5 2
      x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/SlmHealthIndicatorService.java

+ 7 - 0
docs/changelog/92399.yaml

@@ -0,0 +1,7 @@
+pr: 92399
+summary: "[HealthAPI] Add size parameter that controls the number of affected resources\
+  \ returned"
+area: Health
+type: feature
+issues:
+ - 91930

+ 6 - 0
docs/reference/health/health.asciidoc

@@ -92,6 +92,12 @@ for health status set `verbose` to `false` to disable the more expensive analysi
     These details include additional troubleshooting metrics and sometimes a root cause analysis of a health status.
     These details include additional troubleshooting metrics and sometimes a root cause analysis of a health status.
     Defaults to `true`.
     Defaults to `true`.
 
 
+`size`::
+    (Optional, integer) The maximum number of affected resources to return.
+    As a diagnosis can return multiple types of affected resources this parameter will limit the number of resources returned for each type to the configured value (e.g. a diagnosis could return
+    `1000` affected indices and `1000` affected nodes).
+    Defaults to `1000`.
+
 [role="child_attributes"]
 [role="child_attributes"]
 [[health-api-response-body]]
 [[health-api-response-body]]
 ==== {api-response-body-title}
 ==== {api-response-body-title}

+ 1 - 1
qa/smoke-test-http/src/javaRestTest/java/org/elasticsearch/http/HealthRestCancellationIT.java

@@ -111,7 +111,7 @@ public class HealthRestCancellationIT extends HttpSmokeTestCase {
         }
         }
 
 
         @Override
         @Override
-        public HealthIndicatorResult calculate(boolean verbose, HealthInfo healthInfo) {
+        public HealthIndicatorResult calculate(boolean verbose, int maxAffectedResourcesCount, HealthInfo healthInfo) {
             try {
             try {
                 operationBlock.acquire();
                 operationBlock.acquire();
             } catch (InterruptedException e) {
             } catch (InterruptedException e) {

+ 12 - 7
rest-api-spec/src/main/resources/rest-api-spec/api/_internal.health.json

@@ -32,14 +32,19 @@
       ]
       ]
     },
     },
     "params":{
     "params":{
-      "timeout":{
-        "type":"time",
-        "description":"Explicit operation timeout"
+      "timeout": {
+        "type": "time",
+        "description": "Explicit operation timeout"
       },
       },
-      "verbose":{
-        "type":"boolean",
-        "description":"Opt in for more information about the health of the system",
-        "default":true
+      "verbose": {
+        "type": "boolean",
+        "description": "Opt in for more information about the health of the system",
+        "default": true
+      },
+      "size": {
+        "type": "int",
+        "description": "Limit the number of affected resources the health API returns",
+        "default": 1000
       }
       }
     }
     }
   }
   }

+ 2 - 1
server/src/internalClusterTest/java/org/elasticsearch/discovery/StableMasterDisruptionIT.java

@@ -135,7 +135,8 @@ public class StableMasterDisruptionIT extends ESIntegTestCase {
 
 
     private void assertMasterStability(Client client, HealthStatus expectedStatus, Matcher<String> expectedMatcher) throws Exception {
     private void assertMasterStability(Client client, HealthStatus expectedStatus, Matcher<String> expectedMatcher) throws Exception {
         assertBusy(() -> {
         assertBusy(() -> {
-            GetHealthAction.Response healthResponse = client.execute(GetHealthAction.INSTANCE, new GetHealthAction.Request(true)).get();
+            GetHealthAction.Response healthResponse = client.execute(GetHealthAction.INSTANCE, new GetHealthAction.Request(true, 1000))
+                .get();
             String debugInformation = xContentToString(healthResponse);
             String debugInformation = xContentToString(healthResponse);
             assertThat(debugInformation, healthResponse.findIndicator("master_is_stable").status(), equalTo(expectedStatus));
             assertThat(debugInformation, healthResponse.findIndicator("master_is_stable").status(), equalTo(expectedStatus));
             assertThat(debugInformation, healthResponse.findIndicator("master_is_stable").symptom(), expectedMatcher);
             assertThat(debugInformation, healthResponse.findIndicator("master_is_stable").symptom(), expectedMatcher);

+ 7 - 5
server/src/internalClusterTest/java/org/elasticsearch/health/GetHealthActionIT.java

@@ -144,7 +144,7 @@ public class GetHealthActionIT extends ESIntegTestCase {
         }
         }
 
 
         @Override
         @Override
-        public HealthIndicatorResult calculate(boolean verbose, HealthInfo healthInfo) {
+        public HealthIndicatorResult calculate(boolean verbose, int maxAffectedResourcesCount, HealthInfo healthInfo) {
             var status = clusterService.getClusterSettings().get(statusSetting);
             var status = clusterService.getClusterSettings().get(statusSetting);
             return createIndicator(
             return createIndicator(
                 status,
                 status,
@@ -203,8 +203,10 @@ public class GetHealthActionIT extends ESIntegTestCase {
             {
             {
                 ExecutionException exception = expectThrows(
                 ExecutionException exception = expectThrows(
                     ExecutionException.class,
                     ExecutionException.class,
-                    () -> client.execute(GetHealthAction.INSTANCE, new GetHealthAction.Request(NONEXISTENT_INDICATOR_NAME, randomBoolean()))
-                        .get()
+                    () -> client.execute(
+                        GetHealthAction.INSTANCE,
+                        new GetHealthAction.Request(NONEXISTENT_INDICATOR_NAME, randomBoolean(), 1000)
+                    ).get()
                 );
                 );
                 assertThat(exception.getCause(), instanceOf(ResourceNotFoundException.class));
                 assertThat(exception.getCause(), instanceOf(ResourceNotFoundException.class));
             }
             }
@@ -258,7 +260,7 @@ public class GetHealthActionIT extends ESIntegTestCase {
         HealthStatus clusterCoordinationIndicatorStatus,
         HealthStatus clusterCoordinationIndicatorStatus,
         boolean verbose
         boolean verbose
     ) throws Exception {
     ) throws Exception {
-        var response = client.execute(GetHealthAction.INSTANCE, new GetHealthAction.Request(verbose)).get();
+        var response = client.execute(GetHealthAction.INSTANCE, new GetHealthAction.Request(verbose, 1000)).get();
 
 
         assertThat(
         assertThat(
             response.getStatus(),
             response.getStatus(),
@@ -294,7 +296,7 @@ public class GetHealthActionIT extends ESIntegTestCase {
     }
     }
 
 
     private void testIndicator(Client client, HealthStatus ilmIndicatorStatus, boolean verbose) throws Exception {
     private void testIndicator(Client client, HealthStatus ilmIndicatorStatus, boolean verbose) throws Exception {
-        var response = client.execute(GetHealthAction.INSTANCE, new GetHealthAction.Request(ILM_INDICATOR_NAME, verbose)).get();
+        var response = client.execute(GetHealthAction.INSTANCE, new GetHealthAction.Request(ILM_INDICATOR_NAME, verbose, 1000)).get();
         assertNull(response.getStatus());
         assertNull(response.getStatus());
         assertThat(response.getClusterName(), equalTo(new ClusterName(cluster().getClusterName())));
         assertThat(response.getClusterName(), equalTo(new ClusterName(cluster().getClusterName())));
         assertThat(
         assertThat(

+ 2 - 2
server/src/internalClusterTest/java/org/elasticsearch/health/HealthServiceIT.java

@@ -92,7 +92,7 @@ public class HealthServiceIT extends ESIntegTestCase {
                         throw new RuntimeException(e);
                         throw new RuntimeException(e);
                     }
                     }
                 };
                 };
-                healthService.getHealth(internalCluster.client(node), TestHealthIndicatorService.NAME, true, listener);
+                healthService.getHealth(internalCluster.client(node), TestHealthIndicatorService.NAME, true, 1000, listener);
                 assertBusy(() -> assertThat(onResponseCalled.get(), equalTo(true)));
                 assertBusy(() -> assertThat(onResponseCalled.get(), equalTo(true)));
             }
             }
         }
         }
@@ -158,7 +158,7 @@ public class HealthServiceIT extends ESIntegTestCase {
         }
         }
 
 
         @Override
         @Override
-        public HealthIndicatorResult calculate(boolean verbose, HealthInfo healthInfo) {
+        public HealthIndicatorResult calculate(boolean verbose, int maxAffectedResourcesCount, HealthInfo healthInfo) {
             assertThat(healthInfo.diskInfoByNode().size(), equalTo(internalCluster().getNodeNames().length));
             assertThat(healthInfo.diskInfoByNode().size(), equalTo(internalCluster().getNodeNames().length));
             for (DiskHealthInfo diskHealthInfo : healthInfo.diskInfoByNode().values()) {
             for (DiskHealthInfo diskHealthInfo : healthInfo.diskInfoByNode().values()) {
                 assertThat(diskHealthInfo.healthStatus(), equalTo(HealthStatus.GREEN));
                 assertThat(diskHealthInfo.healthStatus(), equalTo(HealthStatus.GREEN));

+ 1 - 1
server/src/internalClusterTest/java/org/elasticsearch/health/node/DiskHealthIndicatorServiceIT.java

@@ -81,7 +81,7 @@ public class DiskHealthIndicatorServiceIT extends ESIntegTestCase {
                 throw new RuntimeException(e);
                 throw new RuntimeException(e);
             }
             }
         };
         };
-        healthService.getHealth(internalCluster().client(node), DiskHealthIndicatorService.NAME, true, listener);
+        healthService.getHealth(internalCluster().client(node), DiskHealthIndicatorService.NAME, true, 1000, listener);
         assertBusy(() -> assertNotNull(resultListReference.get()));
         assertBusy(() -> assertNotNull(resultListReference.get()));
         return resultListReference.get();
         return resultListReference.get();
     }
     }

+ 1 - 1
server/src/internalClusterTest/java/org/elasticsearch/snapshots/RepositoryIntegrityHealthIndicatorServiceIT.java

@@ -67,7 +67,7 @@ public class RepositoryIntegrityHealthIndicatorServiceIT extends AbstractSnapsho
     }
     }
 
 
     private void assertSnapshotRepositoryHealth(String message, Client client, HealthStatus status) {
     private void assertSnapshotRepositoryHealth(String message, Client client, HealthStatus status) {
-        var response = client.execute(GetHealthAction.INSTANCE, new GetHealthAction.Request(randomBoolean())).actionGet();
+        var response = client.execute(GetHealthAction.INSTANCE, new GetHealthAction.Request(randomBoolean(), 1000)).actionGet();
         assertThat(message, response.findIndicator(NAME).status(), equalTo(status));
         assertThat(message, response.findIndicator(NAME).status(), equalTo(status));
     }
     }
 
 

+ 1 - 1
server/src/main/java/org/elasticsearch/cluster/coordination/StableMasterHealthIndicatorService.java

@@ -104,7 +104,7 @@ public class StableMasterHealthIndicatorService implements HealthIndicatorServic
     }
     }
 
 
     @Override
     @Override
-    public HealthIndicatorResult calculate(boolean verbose, HealthInfo healthInfo) {
+    public HealthIndicatorResult calculate(boolean verbose, int maxAffectedResourcesCount, HealthInfo healthInfo) {
         CoordinationDiagnosticsService.CoordinationDiagnosticsResult coordinationDiagnosticsResult = coordinationDiagnosticsService
         CoordinationDiagnosticsService.CoordinationDiagnosticsResult coordinationDiagnosticsResult = coordinationDiagnosticsService
             .diagnoseMasterStability(verbose);
             .diagnoseMasterStability(verbose);
         return getHealthIndicatorResult(coordinationDiagnosticsResult, verbose);
         return getHealthIndicatorResult(coordinationDiagnosticsResult, verbose);

+ 5 - 3
server/src/main/java/org/elasticsearch/cluster/routing/allocation/ShardsAvailabilityHealthIndicatorService.java

@@ -107,7 +107,7 @@ public class ShardsAvailabilityHealthIndicatorService implements HealthIndicator
     }
     }
 
 
     @Override
     @Override
-    public HealthIndicatorResult calculate(boolean verbose, HealthInfo healthInfo) {
+    public HealthIndicatorResult calculate(boolean verbose, int maxAffectedResourcesCount, HealthInfo healthInfo) {
         var state = clusterService.state();
         var state = clusterService.state();
         var shutdown = state.getMetadata().custom(NodesShutdownMetadata.TYPE, NodesShutdownMetadata.EMPTY);
         var shutdown = state.getMetadata().custom(NodesShutdownMetadata.TYPE, NodesShutdownMetadata.EMPTY);
         var status = new ShardAllocationStatus(state.getMetadata());
         var status = new ShardAllocationStatus(state.getMetadata());
@@ -126,7 +126,7 @@ public class ShardsAvailabilityHealthIndicatorService implements HealthIndicator
             status.getSymptom(),
             status.getSymptom(),
             status.getDetails(verbose),
             status.getDetails(verbose),
             status.getImpacts(),
             status.getImpacts(),
-            status.getDiagnosis(verbose)
+            status.getDiagnosis(verbose, maxAffectedResourcesCount)
         );
         );
     }
     }
 
 
@@ -893,9 +893,10 @@ public class ShardsAvailabilityHealthIndicatorService implements HealthIndicator
         /**
         /**
          * Returns the diagnosis for unassigned primary and replica shards.
          * Returns the diagnosis for unassigned primary and replica shards.
          * @param verbose true if the diagnosis should be generated, false if they should be omitted.
          * @param verbose true if the diagnosis should be generated, false if they should be omitted.
+         * @param maxAffectedResourcesCount the max number of affected resources to be returned as part of the diagnosis
          * @return The diagnoses list the indicator identified. Alternatively, an empty list if none were found or verbose is false.
          * @return The diagnoses list the indicator identified. Alternatively, an empty list if none were found or verbose is false.
          */
          */
-        public List<Diagnosis> getDiagnosis(boolean verbose) {
+        public List<Diagnosis> getDiagnosis(boolean verbose, int maxAffectedResourcesCount) {
             if (verbose) {
             if (verbose) {
                 Map<Diagnosis.Definition, Set<String>> diagnosisToAffectedIndices = new HashMap<>(primaries.diagnosisDefinitions);
                 Map<Diagnosis.Definition, Set<String>> diagnosisToAffectedIndices = new HashMap<>(primaries.diagnosisDefinitions);
                 replicas.diagnosisDefinitions.forEach((diagnosisDef, indicesWithReplicasUnassigned) -> {
                 replicas.diagnosisDefinitions.forEach((diagnosisDef, indicesWithReplicasUnassigned) -> {
@@ -920,6 +921,7 @@ public class ShardsAvailabilityHealthIndicatorService implements HealthIndicator
                                         e.getValue()
                                         e.getValue()
                                             .stream()
                                             .stream()
                                             .sorted(indicesComparatorByPriorityAndName(clusterMetadata))
                                             .sorted(indicesComparatorByPriorityAndName(clusterMetadata))
+                                            .limit(Math.min(e.getValue().size(), maxAffectedResourcesCount))
                                             .collect(Collectors.toList())
                                             .collect(Collectors.toList())
                                     )
                                     )
                                 )
                                 )

+ 27 - 11
server/src/main/java/org/elasticsearch/health/GetHealthAction.java

@@ -37,6 +37,8 @@ import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Objects;
 
 
+import static org.elasticsearch.action.ValidateActions.addValidationError;
+
 public class GetHealthAction extends ActionType<GetHealthAction.Response> {
 public class GetHealthAction extends ActionType<GetHealthAction.Response> {
 
 
     public static final GetHealthAction INSTANCE = new GetHealthAction();
     public static final GetHealthAction INSTANCE = new GetHealthAction();
@@ -146,21 +148,25 @@ public class GetHealthAction extends ActionType<GetHealthAction.Response> {
     public static class Request extends ActionRequest {
     public static class Request extends ActionRequest {
         private final String indicatorName;
         private final String indicatorName;
         private final boolean verbose;
         private final boolean verbose;
+        private final int size;
 
 
-        public Request(boolean verbose) {
-            // We never compute details if no indicator name is given because of the runtime cost:
-            this.indicatorName = null;
-            this.verbose = verbose;
+        public Request(boolean verbose, int size) {
+            this(null, verbose, size);
         }
         }
 
 
-        public Request(String indicatorName, boolean verbose) {
+        public Request(String indicatorName, boolean verbose, int size) {
             this.indicatorName = indicatorName;
             this.indicatorName = indicatorName;
             this.verbose = verbose;
             this.verbose = verbose;
+            this.size = size;
         }
         }
 
 
         @Override
         @Override
         public ActionRequestValidationException validate() {
         public ActionRequestValidationException validate() {
-            return null;
+            ActionRequestValidationException validationException = null;
+            if (size < 0) {
+                validationException = addValidationError("The size parameter must be a positive integer", validationException);
+            }
+            return validationException;
         }
         }
 
 
         @Override
         @Override
@@ -195,11 +201,21 @@ public class GetHealthAction extends ActionType<GetHealthAction.Response> {
         @Override
         @Override
         protected void doExecute(Task task, Request request, ActionListener<Response> responseListener) {
         protected void doExecute(Task task, Request request, ActionListener<Response> responseListener) {
             assert task instanceof CancellableTask;
             assert task instanceof CancellableTask;
-            healthService.getHealth(client, request.indicatorName, request.verbose, responseListener.map(healthIndicatorResults -> {
-                Response response = new Response(clusterService.getClusterName(), healthIndicatorResults, request.indicatorName == null);
-                healthApiStats.track(request.verbose, response);
-                return response;
-            }));
+            healthService.getHealth(
+                client,
+                request.indicatorName,
+                request.verbose,
+                request.size,
+                responseListener.map(healthIndicatorResults -> {
+                    Response response = new Response(
+                        clusterService.getClusterName(),
+                        healthIndicatorResults,
+                        request.indicatorName == null
+                    );
+                    healthApiStats.track(request.verbose, response);
+                    return response;
+                })
+            );
         }
         }
     }
     }
 }
 }

+ 5 - 1
server/src/main/java/org/elasticsearch/health/HealthIndicatorService.java

@@ -22,7 +22,11 @@ public interface HealthIndicatorService {
 
 
     String name();
     String name();
 
 
-    HealthIndicatorResult calculate(boolean verbose, HealthInfo healthInfo);
+    default HealthIndicatorResult calculate(boolean verbose, HealthInfo healthInfo) {
+        return calculate(verbose, 1000, healthInfo);
+    }
+
+    HealthIndicatorResult calculate(boolean verbose, int maxAffectedResourcesCount, HealthInfo healthInfo);
 
 
     /**
     /**
      * This method creates a HealthIndicatorResult with the given information. Note that it sorts the impacts by severity (the lower the
      * This method creates a HealthIndicatorResult with the given information. Note that it sorts the impacts by severity (the lower the

+ 12 - 7
server/src/main/java/org/elasticsearch/health/HealthService.java

@@ -76,20 +76,25 @@ public class HealthService {
      *
      *
      * @param client        A client to be used to fetch the health data from the health node
      * @param client        A client to be used to fetch the health data from the health node
      * @param indicatorName If not null, the returned results will only have this indicator
      * @param indicatorName If not null, the returned results will only have this indicator
-     * @param explain       Whether to compute the details portion of the results
+     * @param verbose       Whether to compute the details portion of the results
      * @param listener      A listener to be notified of the list of all HealthIndicatorResult if indicatorName is null, or one
      * @param listener      A listener to be notified of the list of all HealthIndicatorResult if indicatorName is null, or one
      *                      HealthIndicatorResult if indicatorName is not null
      *                      HealthIndicatorResult if indicatorName is not null
+     * @param maxAffectedResourcesCount The maximum number of affected resources to return per each type.
      * @throws ResourceNotFoundException if an indicator name is given and the indicator is not found
      * @throws ResourceNotFoundException if an indicator name is given and the indicator is not found
      */
      */
     public void getHealth(
     public void getHealth(
         Client client,
         Client client,
         @Nullable String indicatorName,
         @Nullable String indicatorName,
-        boolean explain,
+        boolean verbose,
+        int maxAffectedResourcesCount,
         ActionListener<List<HealthIndicatorResult>> listener
         ActionListener<List<HealthIndicatorResult>> listener
     ) {
     ) {
+        if (maxAffectedResourcesCount < 0) {
+            throw new IllegalArgumentException("The max number of resources must be a positive integer");
+        }
         // Determine if cluster is stable enough to calculate health before running other indicators
         // Determine if cluster is stable enough to calculate health before running other indicators
         List<HealthIndicatorResult> preflightResults = preflightHealthIndicatorServices.stream()
         List<HealthIndicatorResult> preflightResults = preflightHealthIndicatorServices.stream()
-            .map(service -> service.calculate(explain, HealthInfo.EMPTY_HEALTH_INFO))
+            .map(service -> service.calculate(verbose, maxAffectedResourcesCount, HealthInfo.EMPTY_HEALTH_INFO))
             .toList();
             .toList();
 
 
         // If any of these are not GREEN, then we cannot obtain health from other indicators
         // If any of these are not GREEN, then we cannot obtain health from other indicators
@@ -113,7 +118,7 @@ public class HealthService {
                     ActionRunnable<List<HealthIndicatorResult>> calculateFilteredIndicatorsRunnable = calculateFilteredIndicatorsRunnable(
                     ActionRunnable<List<HealthIndicatorResult>> calculateFilteredIndicatorsRunnable = calculateFilteredIndicatorsRunnable(
                         indicatorName,
                         indicatorName,
                         healthInfo,
                         healthInfo,
-                        explain,
+                        verbose,
                         listener
                         listener
                     );
                     );
 
 
@@ -131,7 +136,7 @@ public class HealthService {
                     ActionRunnable<List<HealthIndicatorResult>> calculateFilteredIndicatorsRunnable = calculateFilteredIndicatorsRunnable(
                     ActionRunnable<List<HealthIndicatorResult>> calculateFilteredIndicatorsRunnable = calculateFilteredIndicatorsRunnable(
                         indicatorName,
                         indicatorName,
                         HealthInfo.EMPTY_HEALTH_INFO,
                         HealthInfo.EMPTY_HEALTH_INFO,
-                        explain,
+                        verbose,
                         listener
                         listener
                     );
                     );
                     try {
                     try {
@@ -150,7 +155,7 @@ public class HealthService {
                     return ActionRunnable.wrap(listener, l -> {
                     return ActionRunnable.wrap(listener, l -> {
                         List<HealthIndicatorResult> results = Stream.concat(
                         List<HealthIndicatorResult> results = Stream.concat(
                             filteredPreflightResults,
                             filteredPreflightResults,
-                            filteredIndicators.map(service -> service.calculate(explain, healthInfo))
+                            filteredIndicators.map(service -> service.calculate(explain, maxAffectedResourcesCount, healthInfo))
                         ).toList();
                         ).toList();
 
 
                         validateResultsAndNotifyListener(indicatorName, results, l);
                         validateResultsAndNotifyListener(indicatorName, results, l);
@@ -160,7 +165,7 @@ public class HealthService {
 
 
         } else {
         } else {
             // Mark remaining indicators as UNKNOWN
             // Mark remaining indicators as UNKNOWN
-            HealthIndicatorDetails unknownDetails = healthUnknownReason(preflightResults, explain);
+            HealthIndicatorDetails unknownDetails = healthUnknownReason(preflightResults, verbose);
             Stream<HealthIndicatorResult> filteredIndicatorResults = filteredIndicators.map(
             Stream<HealthIndicatorResult> filteredIndicatorResults = filteredIndicators.map(
                 service -> generateUnknownResult(service, UNKNOWN_RESULT_SUMMARY_PREFLIGHT_FAILED, unknownDetails)
                 service -> generateUnknownResult(service, UNKNOWN_RESULT_SUMMARY_PREFLIGHT_FAILED, unknownDetails)
             );
             );

+ 4 - 1
server/src/main/java/org/elasticsearch/health/RestGetHealthAction.java

@@ -23,6 +23,8 @@ public class RestGetHealthAction extends BaseRestHandler {
 
 
     private static final String VERBOSE_PARAM = "verbose";
     private static final String VERBOSE_PARAM = "verbose";
 
 
+    private static final String SIZE_PARAM = "size";
+
     @Override
     @Override
     public String getName() {
     public String getName() {
         // TODO: Existing - "cluster_health_action", "cat_health_action"
         // TODO: Existing - "cluster_health_action", "cat_health_action"
@@ -38,7 +40,8 @@ public class RestGetHealthAction extends BaseRestHandler {
     protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
     protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
         String indicatorName = request.param("indicator");
         String indicatorName = request.param("indicator");
         boolean verbose = request.paramAsBoolean(VERBOSE_PARAM, true);
         boolean verbose = request.paramAsBoolean(VERBOSE_PARAM, true);
-        GetHealthAction.Request getHealthRequest = new GetHealthAction.Request(indicatorName, verbose);
+        int size = request.paramAsInt(SIZE_PARAM, 1000);
+        GetHealthAction.Request getHealthRequest = new GetHealthAction.Request(indicatorName, verbose, size);
         return channel -> new RestCancellableNodeClient(client, request.getHttpChannel()).execute(
         return channel -> new RestCancellableNodeClient(client, request.getHttpChannel()).execute(
             GetHealthAction.INSTANCE,
             GetHealthAction.INSTANCE,
             getHealthRequest,
             getHealthRequest,

+ 12 - 11
server/src/main/java/org/elasticsearch/health/node/DiskHealthIndicatorService.java

@@ -41,6 +41,7 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import java.util.stream.Stream;
 
 
 import static org.elasticsearch.cluster.node.DiscoveryNode.DISCOVERY_NODE_COMPARATOR;
 import static org.elasticsearch.cluster.node.DiscoveryNode.DISCOVERY_NODE_COMPARATOR;
+import static org.elasticsearch.common.util.CollectionUtils.limitSize;
 import static org.elasticsearch.health.node.HealthIndicatorDisplayValues.are;
 import static org.elasticsearch.health.node.HealthIndicatorDisplayValues.are;
 import static org.elasticsearch.health.node.HealthIndicatorDisplayValues.getSortedUniqueValuesString;
 import static org.elasticsearch.health.node.HealthIndicatorDisplayValues.getSortedUniqueValuesString;
 import static org.elasticsearch.health.node.HealthIndicatorDisplayValues.getTruncatedIndices;
 import static org.elasticsearch.health.node.HealthIndicatorDisplayValues.getTruncatedIndices;
@@ -68,7 +69,6 @@ public class DiskHealthIndicatorService implements HealthIndicatorService {
     private static final String IMPACT_INGEST_AT_RISK_ID = "ingest_capability_at_risk";
     private static final String IMPACT_INGEST_AT_RISK_ID = "ingest_capability_at_risk";
     private static final String IMPACT_CLUSTER_STABILITY_AT_RISK_ID = "cluster_stability_at_risk";
     private static final String IMPACT_CLUSTER_STABILITY_AT_RISK_ID = "cluster_stability_at_risk";
     private static final String IMPACT_CLUSTER_FUNCTIONALITY_UNAVAILABLE_ID = "cluster_functionality_unavailable";
     private static final String IMPACT_CLUSTER_FUNCTIONALITY_UNAVAILABLE_ID = "cluster_functionality_unavailable";
-    private static final String IMPACT_DATA_NODE_WITHOUT_DISK_SPACE = "data_node_without_disk_space";
 
 
     private final ClusterService clusterService;
     private final ClusterService clusterService;
 
 
@@ -82,7 +82,7 @@ public class DiskHealthIndicatorService implements HealthIndicatorService {
     }
     }
 
 
     @Override
     @Override
-    public HealthIndicatorResult calculate(boolean verbose, HealthInfo healthInfo) {
+    public HealthIndicatorResult calculate(boolean verbose, int maxAffectedResourcesCount, HealthInfo healthInfo) {
         Map<String, DiskHealthInfo> diskHealthInfoMap = healthInfo.diskInfoByNode();
         Map<String, DiskHealthInfo> diskHealthInfoMap = healthInfo.diskInfoByNode();
         if (diskHealthInfoMap == null || diskHealthInfoMap.isEmpty()) {
         if (diskHealthInfoMap == null || diskHealthInfoMap.isEmpty()) {
             /*
             /*
@@ -107,7 +107,7 @@ public class DiskHealthIndicatorService implements HealthIndicatorService {
             diskHealthAnalyzer.getSymptom(),
             diskHealthAnalyzer.getSymptom(),
             diskHealthAnalyzer.getDetails(verbose),
             diskHealthAnalyzer.getDetails(verbose),
             diskHealthAnalyzer.getImpacts(),
             diskHealthAnalyzer.getImpacts(),
-            diskHealthAnalyzer.getDiagnoses()
+            diskHealthAnalyzer.getDiagnoses(maxAffectedResourcesCount)
         );
         );
     }
     }
 
 
@@ -344,7 +344,7 @@ public class DiskHealthIndicatorService implements HealthIndicatorService {
             return impacts;
             return impacts;
         }
         }
 
 
-        private List<Diagnosis> getDiagnoses() {
+        private List<Diagnosis> getDiagnoses(int size) {
             if (healthStatus == HealthStatus.GREEN) {
             if (healthStatus == HealthStatus.GREEN) {
                 return List.of();
                 return List.of();
             }
             }
@@ -353,7 +353,7 @@ public class DiskHealthIndicatorService implements HealthIndicatorService {
                 Set<String> affectedIndices = Sets.union(blockedIndices, indicesAtRisk);
                 Set<String> affectedIndices = Sets.union(blockedIndices, indicesAtRisk);
                 List<Diagnosis.Resource> affectedResources = new ArrayList<>();
                 List<Diagnosis.Resource> affectedResources = new ArrayList<>();
                 if (dataNodes.size() > 0) {
                 if (dataNodes.size() > 0) {
-                    Diagnosis.Resource nodeResources = new Diagnosis.Resource(dataNodes);
+                    Diagnosis.Resource nodeResources = new Diagnosis.Resource(limitSize(dataNodes, size));
                     affectedResources.add(nodeResources);
                     affectedResources.add(nodeResources);
                 }
                 }
                 if (affectedIndices.size() > 0) {
                 if (affectedIndices.size() > 0) {
@@ -361,6 +361,7 @@ public class DiskHealthIndicatorService implements HealthIndicatorService {
                         Diagnosis.Resource.Type.INDEX,
                         Diagnosis.Resource.Type.INDEX,
                         affectedIndices.stream()
                         affectedIndices.stream()
                             .sorted(indicesComparatorByPriorityAndName(clusterState.metadata()))
                             .sorted(indicesComparatorByPriorityAndName(clusterState.metadata()))
+                            .limit(Math.min(affectedIndices.size(), size))
                             .collect(Collectors.toList())
                             .collect(Collectors.toList())
                     );
                     );
                     affectedResources.add(indexResources);
                     affectedResources.add(indexResources);
@@ -405,16 +406,16 @@ public class DiskHealthIndicatorService implements HealthIndicatorService {
                 }
                 }
             }
             }
             if (masterNodes.containsKey(HealthStatus.RED)) {
             if (masterNodes.containsKey(HealthStatus.RED)) {
-                diagnosisList.add(createNonDataNodeDiagnosis(HealthStatus.RED, masterNodes.get(HealthStatus.RED), true));
+                diagnosisList.add(createNonDataNodeDiagnosis(HealthStatus.RED, masterNodes.get(HealthStatus.RED), size, true));
             }
             }
             if (masterNodes.containsKey(HealthStatus.YELLOW)) {
             if (masterNodes.containsKey(HealthStatus.YELLOW)) {
-                diagnosisList.add(createNonDataNodeDiagnosis(HealthStatus.YELLOW, masterNodes.get(HealthStatus.YELLOW), true));
+                diagnosisList.add(createNonDataNodeDiagnosis(HealthStatus.YELLOW, masterNodes.get(HealthStatus.YELLOW), size, true));
             }
             }
             if (otherNodes.containsKey(HealthStatus.RED)) {
             if (otherNodes.containsKey(HealthStatus.RED)) {
-                diagnosisList.add(createNonDataNodeDiagnosis(HealthStatus.RED, otherNodes.get(HealthStatus.RED), false));
+                diagnosisList.add(createNonDataNodeDiagnosis(HealthStatus.RED, otherNodes.get(HealthStatus.RED), size, false));
             }
             }
             if (otherNodes.containsKey(HealthStatus.YELLOW)) {
             if (otherNodes.containsKey(HealthStatus.YELLOW)) {
-                diagnosisList.add(createNonDataNodeDiagnosis(HealthStatus.YELLOW, otherNodes.get(HealthStatus.YELLOW), false));
+                diagnosisList.add(createNonDataNodeDiagnosis(HealthStatus.YELLOW, otherNodes.get(HealthStatus.YELLOW), size, false));
             }
             }
             return diagnosisList;
             return diagnosisList;
         }
         }
@@ -487,7 +488,7 @@ public class DiskHealthIndicatorService implements HealthIndicatorService {
                 .collect(Collectors.toSet());
                 .collect(Collectors.toSet());
         }
         }
 
 
-        private Diagnosis createNonDataNodeDiagnosis(HealthStatus healthStatus, Collection<DiscoveryNode> nodes, boolean isMaster) {
+        private Diagnosis createNonDataNodeDiagnosis(HealthStatus healthStatus, List<DiscoveryNode> nodes, int size, boolean isMaster) {
             return new Diagnosis(
             return new Diagnosis(
                 new Diagnosis.Definition(
                 new Diagnosis.Definition(
                     NAME,
                     NAME,
@@ -496,7 +497,7 @@ public class DiskHealthIndicatorService implements HealthIndicatorService {
                     "Please add capacity to the current nodes, or replace them with ones with higher capacity.",
                     "Please add capacity to the current nodes, or replace them with ones with higher capacity.",
                     isMaster ? "https://ela.st/fix-master-disk" : "https://ela.st/fix-disk-space"
                     isMaster ? "https://ela.st/fix-master-disk" : "https://ela.st/fix-disk-space"
                 ),
                 ),
-                List.of(new Diagnosis.Resource(nodes))
+                List.of(new Diagnosis.Resource(limitSize(nodes, size)))
             );
             );
         }
         }
 
 

+ 7 - 2
server/src/main/java/org/elasticsearch/snapshots/RepositoryIntegrityHealthIndicatorService.java

@@ -72,7 +72,7 @@ public class RepositoryIntegrityHealthIndicatorService implements HealthIndicato
     }
     }
 
 
     @Override
     @Override
-    public HealthIndicatorResult calculate(boolean verbose, HealthInfo healthInfo) {
+    public HealthIndicatorResult calculate(boolean verbose, int maxAffectedResourcesCount, HealthInfo healthInfo) {
         var snapshotMetadata = clusterService.state().metadata().custom(RepositoriesMetadata.TYPE, RepositoriesMetadata.EMPTY);
         var snapshotMetadata = clusterService.state().metadata().custom(RepositoriesMetadata.TYPE, RepositoriesMetadata.EMPTY);
 
 
         if (snapshotMetadata.repositories().isEmpty()) {
         if (snapshotMetadata.repositories().isEmpty()) {
@@ -133,7 +133,12 @@ public class RepositoryIntegrityHealthIndicatorService implements HealthIndicato
                 )
                 )
                 : HealthIndicatorDetails.EMPTY,
                 : HealthIndicatorDetails.EMPTY,
             impacts,
             impacts,
-            List.of(new Diagnosis(CORRUPTED_REPOSITORY, List.of(new Diagnosis.Resource(Type.SNAPSHOT_REPOSITORY, corrupted))))
+            List.of(
+                new Diagnosis(
+                    CORRUPTED_REPOSITORY,
+                    List.of(new Diagnosis.Resource(Type.SNAPSHOT_REPOSITORY, limitSize(corrupted, maxAffectedResourcesCount)))
+                )
+            )
         );
         );
     }
     }
 
 

+ 106 - 36
server/src/test/java/org/elasticsearch/cluster/routing/allocation/ShardsAvailabilityHealthIndicatorServiceTests.java

@@ -105,7 +105,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
             ),
             ),
             List.of()
             List.of()
         );
         );
-        var service = createAllocationHealthIndicatorService(clusterState);
+        var service = createShardsAvailabilityIndicatorService(clusterState);
 
 
         assertThat(
         assertThat(
             service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO),
             service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO),
@@ -135,7 +135,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
             ),
             ),
             List.of()
             List.of()
         );
         );
-        var service = createAllocationHealthIndicatorService(clusterState);
+        var service = createShardsAvailabilityIndicatorService(clusterState);
 
 
         assertThat(
         assertThat(
             service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO),
             service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO),
@@ -175,7 +175,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
             List.of(index("red-index", new ShardAllocation(randomNodeId(), UNAVAILABLE), new ShardAllocation(randomNodeId(), AVAILABLE))),
             List.of(index("red-index", new ShardAllocation(randomNodeId(), UNAVAILABLE), new ShardAllocation(randomNodeId(), AVAILABLE))),
             List.of()
             List.of()
         );
         );
-        var service = createAllocationHealthIndicatorService(clusterState);
+        var service = createShardsAvailabilityIndicatorService(clusterState);
 
 
         assertThat(
         assertThat(
             service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO),
             service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO),
@@ -203,7 +203,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
 
 
     public void testShouldBeRedWhenThereAreUnassignedPrimariesAndNoReplicas() {
     public void testShouldBeRedWhenThereAreUnassignedPrimariesAndNoReplicas() {
         var clusterState = createClusterStateWith(List.of(index("red-index", new ShardAllocation(randomNodeId(), UNAVAILABLE))), List.of());
         var clusterState = createClusterStateWith(List.of(index("red-index", new ShardAllocation(randomNodeId(), UNAVAILABLE))), List.of());
-        var service = createAllocationHealthIndicatorService(clusterState);
+        var service = createShardsAvailabilityIndicatorService(clusterState);
 
 
         assertThat(
         assertThat(
             service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO),
             service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO),
@@ -234,7 +234,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
             List.of(index("red-index", new ShardAllocation(randomNodeId(), UNAVAILABLE), new ShardAllocation(randomNodeId(), UNAVAILABLE))),
             List.of(index("red-index", new ShardAllocation(randomNodeId(), UNAVAILABLE), new ShardAllocation(randomNodeId(), UNAVAILABLE))),
             List.of()
             List.of()
         );
         );
-        var service = createAllocationHealthIndicatorService(clusterState);
+        var service = createShardsAvailabilityIndicatorService(clusterState);
 
 
         HealthIndicatorResult result = service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO);
         HealthIndicatorResult result = service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO);
         assertEquals(RED, result.status());
         assertEquals(RED, result.status());
@@ -266,7 +266,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
             List.of(),
             List.of(),
             List.of()
             List.of()
         );
         );
-        var service = createAllocationHealthIndicatorService(clusterState);
+        var service = createShardsAvailabilityIndicatorService(clusterState);
 
 
         HealthIndicatorResult result = service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO);
         HealthIndicatorResult result = service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO);
         assertEquals(RED, result.status());
         assertEquals(RED, result.status());
@@ -314,7 +314,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
             List.of(),
             List.of(),
             List.of()
             List.of()
         );
         );
-        var service = createAllocationHealthIndicatorService(clusterState);
+        var service = createShardsAvailabilityIndicatorService(clusterState);
 
 
         HealthIndicatorResult result = service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO);
         HealthIndicatorResult result = service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO);
         // index-2 has the higher priority so it ought to be listed first, followed by index-1 then index-3 which have the same priority:
         // index-2 has the higher priority so it ought to be listed first, followed by index-1 then index-3 which have the same priority:
@@ -344,7 +344,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
             ),
             ),
             List.of(new NodeShutdown("node-0", RESTART, 60))
             List.of(new NodeShutdown("node-0", RESTART, 60))
         );
         );
-        var service = createAllocationHealthIndicatorService(clusterState);
+        var service = createShardsAvailabilityIndicatorService(clusterState);
 
 
         assertThat(
         assertThat(
             service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO),
             service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO),
@@ -365,7 +365,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
             List.of(index("primaries-only-index", new ShardAllocation(randomNodeId(), AVAILABLE))),
             List.of(index("primaries-only-index", new ShardAllocation(randomNodeId(), AVAILABLE))),
             List.of(new NodeShutdown("node-0", RESTART, 60))
             List.of(new NodeShutdown("node-0", RESTART, 60))
         );
         );
-        var service = createAllocationHealthIndicatorService(clusterState);
+        var service = createShardsAvailabilityIndicatorService(clusterState);
 
 
         assertThat(
         assertThat(
             service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO),
             service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO),
@@ -392,7 +392,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
             ),
             ),
             List.of(new NodeShutdown("node-0", RESTART, 60))
             List.of(new NodeShutdown("node-0", RESTART, 60))
         );
         );
-        var service = createAllocationHealthIndicatorService(clusterState);
+        var service = createShardsAvailabilityIndicatorService(clusterState);
 
 
         assertThat(
         assertThat(
             service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO),
             service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO),
@@ -427,7 +427,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
             List.of(index("restarting-index", new ShardAllocation("node-0", INITIALIZING))),
             List.of(index("restarting-index", new ShardAllocation("node-0", INITIALIZING))),
             List.of()
             List.of()
         );
         );
-        var service = createAllocationHealthIndicatorService(clusterState);
+        var service = createShardsAvailabilityIndicatorService(clusterState);
 
 
         assertThat(
         assertThat(
             service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO),
             service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO),
@@ -448,7 +448,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
             List.of(index("restarting-index", new ShardAllocation("node-0", RESTARTING, System.nanoTime()))),
             List.of(index("restarting-index", new ShardAllocation("node-0", RESTARTING, System.nanoTime()))),
             List.of(new NodeShutdown("node-0", RESTART, 60))
             List.of(new NodeShutdown("node-0", RESTART, 60))
         );
         );
-        var service = createAllocationHealthIndicatorService(clusterState);
+        var service = createShardsAvailabilityIndicatorService(clusterState);
 
 
         assertThat(
         assertThat(
             service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO),
             service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO),
@@ -474,7 +474,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
             ),
             ),
             List.of(new NodeShutdown("node-0", RESTART, 60))
             List.of(new NodeShutdown("node-0", RESTART, 60))
         );
         );
-        var service = createAllocationHealthIndicatorService(clusterState);
+        var service = createShardsAvailabilityIndicatorService(clusterState);
 
 
         assertThat(
         assertThat(
             service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO),
             service.calculate(true, HealthInfo.EMPTY_HEALTH_INFO),
@@ -519,7 +519,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
             List.of()
             List.of()
         );
         );
 
 
-        var service = createAllocationHealthIndicatorService(clusterState);
+        var service = createShardsAvailabilityIndicatorService(clusterState);
 
 
         assertThat(
         assertThat(
             service.calculate(false, HealthInfo.EMPTY_HEALTH_INFO),
             service.calculate(false, HealthInfo.EMPTY_HEALTH_INFO),
@@ -555,7 +555,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
             new ShardAllocation(randomNodeId(), UNAVAILABLE, noShardCopy())
             new ShardAllocation(randomNodeId(), UNAVAILABLE, noShardCopy())
         );
         );
 
 
-        var service = createAllocationHealthIndicatorService();
+        var service = createShardsAvailabilityIndicatorService();
         List<Diagnosis.Definition> definitions = service.diagnoseUnassignedShardRouting(shardRouting, ClusterState.EMPTY_STATE);
         List<Diagnosis.Definition> definitions = service.diagnoseUnassignedShardRouting(shardRouting, ClusterState.EMPTY_STATE);
 
 
         assertThat(definitions, hasSize(1));
         assertThat(definitions, hasSize(1));
@@ -601,7 +601,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
                 MoveDecision.NOT_TAKEN
                 MoveDecision.NOT_TAKEN
             )
             )
         );
         );
-        var service = createAllocationHealthIndicatorService(clusterState, decisionMap);
+        var service = createShardsAvailabilityIndicatorService(clusterState, decisionMap);
 
 
         // Get the list of user actions that are generated for this unassigned index shard
         // Get the list of user actions that are generated for this unassigned index shard
         ShardRouting shardRouting = clusterState.routingTable().index(indexMetadata.getIndex()).shard(0).primaryShard();
         ShardRouting shardRouting = clusterState.routingTable().index(indexMetadata.getIndex()).shard(0).primaryShard();
@@ -624,7 +624,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
             .numberOfReplicas(0)
             .numberOfReplicas(0)
             .build();
             .build();
 
 
-        var service = createAllocationHealthIndicatorService();
+        var service = createShardsAvailabilityIndicatorService();
 
 
         // Get the list of user actions that are generated for this unassigned index shard
         // Get the list of user actions that are generated for this unassigned index shard
         List<Diagnosis.Definition> actions = service.checkIsAllocationDisabled(
         List<Diagnosis.Definition> actions = service.checkIsAllocationDisabled(
@@ -652,7 +652,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
             .build();
             .build();
 
 
         // Disallow allocations in cluster settings
         // Disallow allocations in cluster settings
-        var service = createAllocationHealthIndicatorService(
+        var service = createShardsAvailabilityIndicatorService(
             Settings.builder().put(EnableAllocationDecider.CLUSTER_ROUTING_ALLOCATION_ENABLE_SETTING.getKey(), "none").build(),
             Settings.builder().put(EnableAllocationDecider.CLUSTER_ROUTING_ALLOCATION_ENABLE_SETTING.getKey(), "none").build(),
             ClusterState.EMPTY_STATE,
             ClusterState.EMPTY_STATE,
             Map.of()
             Map.of()
@@ -689,7 +689,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
             .build();
             .build();
 
 
         // Disallow allocations in cluster settings
         // Disallow allocations in cluster settings
-        var service = createAllocationHealthIndicatorService(
+        var service = createShardsAvailabilityIndicatorService(
             Settings.builder().put(EnableAllocationDecider.CLUSTER_ROUTING_ALLOCATION_ENABLE_SETTING.getKey(), "none").build(),
             Settings.builder().put(EnableAllocationDecider.CLUSTER_ROUTING_ALLOCATION_ENABLE_SETTING.getKey(), "none").build(),
             ClusterState.EMPTY_STATE,
             ClusterState.EMPTY_STATE,
             Map.of()
             Map.of()
@@ -726,7 +726,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
             .numberOfReplicas(0)
             .numberOfReplicas(0)
             .build();
             .build();
 
 
-        var service = createAllocationHealthIndicatorService();
+        var service = createShardsAvailabilityIndicatorService();
 
 
         // Get the list of user actions that are generated for this unassigned index shard
         // Get the list of user actions that are generated for this unassigned index shard
         List<Diagnosis.Definition> actions = service.checkDataTierRelatedIssues(
         List<Diagnosis.Definition> actions = service.checkDataTierRelatedIssues(
@@ -785,7 +785,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
             List.of(),
             List.of(),
             List.of(hotNode)
             List.of(hotNode)
         );
         );
-        var service = createAllocationHealthIndicatorService();
+        var service = createShardsAvailabilityIndicatorService();
 
 
         // Get the list of user actions that are generated for this unassigned index shard
         // Get the list of user actions that are generated for this unassigned index shard
         List<Diagnosis.Definition> actions = service.checkDataTierRelatedIssues(
         List<Diagnosis.Definition> actions = service.checkDataTierRelatedIssues(
@@ -846,7 +846,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
         );
         );
 
 
         // Configure at most 1 shard per node
         // Configure at most 1 shard per node
-        var service = createAllocationHealthIndicatorService(
+        var service = createShardsAvailabilityIndicatorService(
             Settings.builder().put(CLUSTER_TOTAL_SHARDS_PER_NODE_SETTING.getKey(), 1).build(),
             Settings.builder().put(CLUSTER_TOTAL_SHARDS_PER_NODE_SETTING.getKey(), 1).build(),
             clusterState,
             clusterState,
             Map.of()
             Map.of()
@@ -910,7 +910,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
             List.of(),
             List.of(),
             List.of(dataNode)
             List.of(dataNode)
         );
         );
-        var service = createAllocationHealthIndicatorService();
+        var service = createShardsAvailabilityIndicatorService();
 
 
         // Get the list of user actions that are generated for this unassigned index shard
         // Get the list of user actions that are generated for this unassigned index shard
         List<Diagnosis.Definition> actions = service.checkDataTierRelatedIssues(
         List<Diagnosis.Definition> actions = service.checkDataTierRelatedIssues(
@@ -971,7 +971,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
         );
         );
 
 
         // Configure at most 1 shard per node
         // Configure at most 1 shard per node
-        var service = createAllocationHealthIndicatorService(
+        var service = createShardsAvailabilityIndicatorService(
             Settings.builder().put(CLUSTER_TOTAL_SHARDS_PER_NODE_SETTING.getKey(), 1).build(),
             Settings.builder().put(CLUSTER_TOTAL_SHARDS_PER_NODE_SETTING.getKey(), 1).build(),
             clusterState,
             clusterState,
             Map.of()
             Map.of()
@@ -1010,7 +1010,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
             .numberOfReplicas(0)
             .numberOfReplicas(0)
             .build();
             .build();
 
 
-        var service = createAllocationHealthIndicatorService();
+        var service = createShardsAvailabilityIndicatorService();
 
 
         // Get the list of user actions that are generated for this unassigned index shard
         // Get the list of user actions that are generated for this unassigned index shard
         List<Diagnosis.Definition> actions = service.checkDataTierRelatedIssues(
         List<Diagnosis.Definition> actions = service.checkDataTierRelatedIssues(
@@ -1046,7 +1046,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
             .numberOfReplicas(0)
             .numberOfReplicas(0)
             .build();
             .build();
 
 
-        var service = createAllocationHealthIndicatorService();
+        var service = createShardsAvailabilityIndicatorService();
 
 
         // Get the list of user actions that are generated for this unassigned index shard
         // Get the list of user actions that are generated for this unassigned index shard
         List<Diagnosis.Definition> actions = service.checkDataTierRelatedIssues(
         List<Diagnosis.Definition> actions = service.checkDataTierRelatedIssues(
@@ -1081,7 +1081,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
             .numberOfReplicas(0)
             .numberOfReplicas(0)
             .build();
             .build();
 
 
-        var service = createAllocationHealthIndicatorService();
+        var service = createShardsAvailabilityIndicatorService();
 
 
         // Get the list of user actions that are generated for this unassigned index shard
         // Get the list of user actions that are generated for this unassigned index shard
         List<Diagnosis.Definition> actions = service.checkDataTierRelatedIssues(
         List<Diagnosis.Definition> actions = service.checkDataTierRelatedIssues(
@@ -1116,7 +1116,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
             .numberOfReplicas(0)
             .numberOfReplicas(0)
             .build();
             .build();
 
 
-        var service = createAllocationHealthIndicatorService();
+        var service = createShardsAvailabilityIndicatorService();
 
 
         // Get the list of user actions that are generated for this unassigned index shard
         // Get the list of user actions that are generated for this unassigned index shard
         List<Diagnosis.Definition> actions = service.checkDataTierRelatedIssues(
         List<Diagnosis.Definition> actions = service.checkDataTierRelatedIssues(
@@ -1156,7 +1156,7 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
             .numberOfReplicas(0)
             .numberOfReplicas(0)
             .build();
             .build();
 
 
-        var service = createAllocationHealthIndicatorService();
+        var service = createShardsAvailabilityIndicatorService();
 
 
         // Get the list of user actions that are generated for this unassigned index shard
         // Get the list of user actions that are generated for this unassigned index shard
         List<Diagnosis.Definition> actions = service.checkDataTierRelatedIssues(
         List<Diagnosis.Definition> actions = service.checkDataTierRelatedIssues(
@@ -1183,6 +1183,76 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
         assertThat(actions, contains(ACTION_INCREASE_NODE_CAPACITY));
         assertThat(actions, contains(ACTION_INCREASE_NODE_CAPACITY));
     }
     }
 
 
+    public void testLimitNumberOfAffectedResources() {
+        var clusterState = createClusterStateWith(
+            List.of(
+                index("red-index1", new ShardAllocation(randomNodeId(), UNAVAILABLE), new ShardAllocation(randomNodeId(), AVAILABLE)),
+                index("red-index2", new ShardAllocation(randomNodeId(), UNAVAILABLE), new ShardAllocation(randomNodeId(), AVAILABLE)),
+                index("red-index3", new ShardAllocation(randomNodeId(), UNAVAILABLE), new ShardAllocation(randomNodeId(), AVAILABLE)),
+                index("red-index4", new ShardAllocation(randomNodeId(), UNAVAILABLE), new ShardAllocation(randomNodeId(), AVAILABLE)),
+                index("red-index5", new ShardAllocation(randomNodeId(), UNAVAILABLE), new ShardAllocation(randomNodeId(), AVAILABLE))
+            ),
+            List.of()
+        );
+        var service = createShardsAvailabilityIndicatorService(clusterState);
+
+        {
+            // assert the full result to check that details, impacts, and symptoms use the correct count of affected indices (5)
+            assertThat(
+                service.calculate(true, 2, HealthInfo.EMPTY_HEALTH_INFO),
+                equalTo(
+                    createExpectedResult(
+                        RED,
+                        "This cluster has 5 unavailable primary shards.",
+                        Map.of("unassigned_primaries", 5, "started_replicas", 5),
+                        List.of(
+                            new HealthIndicatorImpact(
+                                NAME,
+                                ShardsAvailabilityHealthIndicatorService.PRIMARY_UNASSIGNED_IMPACT_ID,
+                                1,
+                                "Cannot add data to 5 indices [red-index1, red-index2, red-index3, red-index4, red-index5]. Searches might "
+                                    + "return incomplete results.",
+                                List.of(ImpactArea.INGEST, ImpactArea.SEARCH)
+                            )
+                        ),
+                        List.of(
+                            new Diagnosis(
+                                ACTION_CHECK_ALLOCATION_EXPLAIN_API,
+                                List.of(new Diagnosis.Resource(INDEX, List.of("red-index1", "red-index2")))
+                            )
+                        )
+                    )
+                )
+            );
+        }
+
+        {
+            // larger number of affected resources
+            assertThat(
+                service.calculate(true, 2_000, HealthInfo.EMPTY_HEALTH_INFO).diagnosisList(),
+                equalTo(
+                    List.of(
+                        new Diagnosis(
+                            ACTION_CHECK_ALLOCATION_EXPLAIN_API,
+                            List.of(
+                                new Diagnosis.Resource(INDEX, List.of("red-index1", "red-index2", "red-index3", "red-index4", "red-index5"))
+                            )
+                        )
+                    )
+                )
+            );
+
+        }
+
+        {
+            // 0 affected resources
+            assertThat(
+                service.calculate(true, 0, HealthInfo.EMPTY_HEALTH_INFO).diagnosisList(),
+                equalTo(List.of(new Diagnosis(ACTION_CHECK_ALLOCATION_EXPLAIN_API, List.of(new Diagnosis.Resource(INDEX, List.of())))))
+            );
+        }
+    }
+
     private HealthIndicatorResult createExpectedResult(
     private HealthIndicatorResult createExpectedResult(
         HealthStatus status,
         HealthStatus status,
         String symptom,
         String symptom,
@@ -1452,22 +1522,22 @@ public class ShardsAvailabilityHealthIndicatorServiceTests extends ESTestCase {
 
 
     private record ShardRoutingKey(String index, int shard, boolean primary) {}
     private record ShardRoutingKey(String index, int shard, boolean primary) {}
 
 
-    private static ShardsAvailabilityHealthIndicatorService createAllocationHealthIndicatorService() {
-        return createAllocationHealthIndicatorService(ClusterState.EMPTY_STATE, Collections.emptyMap());
+    private static ShardsAvailabilityHealthIndicatorService createShardsAvailabilityIndicatorService() {
+        return createShardsAvailabilityIndicatorService(ClusterState.EMPTY_STATE, Collections.emptyMap());
     }
     }
 
 
-    private static ShardsAvailabilityHealthIndicatorService createAllocationHealthIndicatorService(ClusterState clusterState) {
-        return createAllocationHealthIndicatorService(clusterState, Collections.emptyMap());
+    private static ShardsAvailabilityHealthIndicatorService createShardsAvailabilityIndicatorService(ClusterState clusterState) {
+        return createShardsAvailabilityIndicatorService(clusterState, Collections.emptyMap());
     }
     }
 
 
-    private static ShardsAvailabilityHealthIndicatorService createAllocationHealthIndicatorService(
+    private static ShardsAvailabilityHealthIndicatorService createShardsAvailabilityIndicatorService(
         ClusterState clusterState,
         ClusterState clusterState,
         final Map<ShardRoutingKey, ShardAllocationDecision> decisions
         final Map<ShardRoutingKey, ShardAllocationDecision> decisions
     ) {
     ) {
-        return createAllocationHealthIndicatorService(Settings.EMPTY, clusterState, decisions);
+        return createShardsAvailabilityIndicatorService(Settings.EMPTY, clusterState, decisions);
     }
     }
 
 
-    private static ShardsAvailabilityHealthIndicatorService createAllocationHealthIndicatorService(
+    private static ShardsAvailabilityHealthIndicatorService createShardsAvailabilityIndicatorService(
         Settings nodeSettings,
         Settings nodeSettings,
         ClusterState clusterState,
         ClusterState clusterState,
         final Map<ShardRoutingKey, ShardAllocationDecision> decisions
         final Map<ShardRoutingKey, ShardAllocationDecision> decisions

+ 27 - 0
server/src/test/java/org/elasticsearch/health/GetHealthRequestTests.java

@@ -0,0 +1,27 @@
+/*
+ * 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.health;
+
+import org.elasticsearch.action.ActionRequestValidationException;
+import org.elasticsearch.test.ESTestCase;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+public class GetHealthRequestTests extends ESTestCase {
+
+    public void testValidation() {
+        var req = new GetHealthAction.Request(true, -1);
+        ActionRequestValidationException validationException = req.validate();
+        assertThat(validationException, notNullValue());
+        assertThat(validationException.validationErrors().size(), is(1));
+        assertThat(validationException.validationErrors().get(0), containsString("The size parameter must be a positive integer"));
+    }
+}

+ 1 - 1
server/src/test/java/org/elasticsearch/health/HealthIndicatorServiceTests.java

@@ -90,7 +90,7 @@ public class HealthIndicatorServiceTests extends ESTestCase {
             }
             }
 
 
             @Override
             @Override
-            public HealthIndicatorResult calculate(boolean verbose, HealthInfo healthInfo) {
+            public HealthIndicatorResult calculate(boolean verbose, int maxAffectedResourcesCount, HealthInfo healthInfo) {
                 return null;
                 return null;
             }
             }
         };
         };

+ 18 - 4
server/src/test/java/org/elasticsearch/health/HealthServiceTests.java

@@ -88,6 +88,7 @@ public class HealthServiceTests extends ESTestCase {
             client,
             client,
             indicatorName,
             indicatorName,
             false,
             false,
+            1000,
             getExpectedHealthIndicatorResultsActionListener(onResponseCalled, expectedHealthIndicatorResults)
             getExpectedHealthIndicatorResultsActionListener(onResponseCalled, expectedHealthIndicatorResults)
         );
         );
         assertBusy(() -> assertThat(onResponseCalled.get(), equalTo(true)));
         assertBusy(() -> assertThat(onResponseCalled.get(), equalTo(true)));
@@ -138,11 +139,24 @@ public class HealthServiceTests extends ESTestCase {
         );
         );
     }
     }
 
 
+    @SuppressWarnings("unchecked")
+    public void testValidateSize() {
+        var shardsAvailable = new HealthIndicatorResult("shards_availability", GREEN, null, null, null, null);
+
+        var service = new HealthService(Collections.emptyList(), List.of(createMockHealthIndicatorService(shardsAvailable)), threadPool);
+        NodeClient client = getTestClient(HealthInfo.EMPTY_HEALTH_INFO);
+        IllegalArgumentException illegalArgumentException = expectThrows(
+            IllegalArgumentException.class,
+            () -> service.getHealth(client, null, true, -1, ActionListener.NOOP)
+        );
+        assertThat(illegalArgumentException.getMessage(), is("The max number of resources must be a positive integer"));
+    }
+
     private <T extends Throwable> void assertGetHealthThrowsException(
     private <T extends Throwable> void assertGetHealthThrowsException(
         HealthService service,
         HealthService service,
         NodeClient client,
         NodeClient client,
         String indicatorName,
         String indicatorName,
-        boolean explain,
+        boolean verbose,
         Class<T> expectedType,
         Class<T> expectedType,
         String expectedMessage,
         String expectedMessage,
         boolean expectOnFailCalled
         boolean expectOnFailCalled
@@ -154,7 +168,7 @@ public class HealthServiceTests extends ESTestCase {
             expectedMessage
             expectedMessage
         );
         );
         try {
         try {
-            service.getHealth(client, indicatorName, explain, listener);
+            service.getHealth(client, indicatorName, verbose, 1000, listener);
         } catch (Throwable t) {
         } catch (Throwable t) {
             if (expectOnFailCalled || (expectedType.isInstance(t) == false)) {
             if (expectOnFailCalled || (expectedType.isInstance(t) == false)) {
                 throw new RuntimeException("Unexpected throwable", t);
                 throw new RuntimeException("Unexpected throwable", t);
@@ -325,7 +339,7 @@ public class HealthServiceTests extends ESTestCase {
                 throw new RuntimeException(e);
                 throw new RuntimeException(e);
             }
             }
         };
         };
-        service.getHealth(client, indicatorName, false, listener);
+        service.getHealth(client, indicatorName, false, 1000, listener);
         assertBusy(() -> assertNotNull(resultReference.get()));
         assertBusy(() -> assertNotNull(resultReference.get()));
         return resultReference.get();
         return resultReference.get();
     }
     }
@@ -366,7 +380,7 @@ public class HealthServiceTests extends ESTestCase {
             }
             }
 
 
             @Override
             @Override
-            public HealthIndicatorResult calculate(boolean verbose, HealthInfo healthInfo) {
+            public HealthIndicatorResult calculate(boolean verbose, int maxAffectedResourcesCount, HealthInfo healthInfo) {
                 if (expectedHealthInfo != null) {
                 if (expectedHealthInfo != null) {
                     assertThat(healthInfo, equalTo(expectedHealthInfo));
                     assertThat(healthInfo, equalTo(expectedHealthInfo));
                 }
                 }

+ 81 - 0
server/src/test/java/org/elasticsearch/health/node/DiskHealthIndicatorServiceTests.java

@@ -882,6 +882,87 @@ public class DiskHealthIndicatorServiceTests extends ESTestCase {
         }
         }
     }
     }
 
 
+    public void testLimitNumberOfAffectedResources() {
+        Set<DiscoveryNodeRole> otherRoles = new HashSet<>(randomNonEmptySubsetOf(OTHER_ROLES));
+        Set<DiscoveryNodeRole> dataRoles = new HashSet<>(randomNonEmptySubsetOf(DATA_ROLES));
+        Set<DiscoveryNodeRole> masterRole = Set.of(DiscoveryNodeRole.MASTER_ROLE);
+        Set<DiscoveryNode> dataNodes = createNodes(30, dataRoles);
+        Set<DiscoveryNode> masterNodes = createNodes(20, masterRole);
+        Set<DiscoveryNode> otherNodes = createNodes(10, otherRoles);
+        ClusterService clusterService = createClusterService(Sets.union(Sets.union(dataNodes, masterNodes), otherNodes), true);
+        DiskHealthIndicatorService diskHealthIndicatorService = new DiskHealthIndicatorService(clusterService);
+        int numberOfRedMasterNodes = masterNodes.size();
+        int numberOfRedOtherNodes = otherNodes.size();
+        int numberOfYellowDataNodes = dataNodes.size();
+        HealthInfo healthInfo = createHealthInfo(
+            List.of(
+                new HealthInfoConfig(HealthStatus.YELLOW, numberOfYellowDataNodes, dataNodes),
+                new HealthInfoConfig(HealthStatus.RED, numberOfRedMasterNodes, masterNodes),
+                new HealthInfoConfig(HealthStatus.RED, numberOfRedOtherNodes, otherNodes)
+            )
+        );
+        {
+            HealthIndicatorResult result = diskHealthIndicatorService.calculate(true, 0, healthInfo);
+            List<Diagnosis> diagnosisList = result.diagnosisList();
+            assertThat(diagnosisList.size(), equalTo(3));
+            {
+                Diagnosis diagnosis = diagnosisList.get(0);
+                List<Diagnosis.Resource> dataAffectedResources = diagnosis.affectedResources();
+                assertThat(dataAffectedResources.size(), equalTo(2));
+                assertThat(dataAffectedResources.get(0).getType(), is(Diagnosis.Resource.Type.NODE));
+                assertThat(dataAffectedResources.get(0).getNodes().size(), is(0));
+                assertThat(dataAffectedResources.get(1).getType(), is(Diagnosis.Resource.Type.INDEX));
+                assertThat(dataAffectedResources.get(1).getValues().size(), is(0));
+            }
+            {
+                Diagnosis diagnosis = diagnosisList.get(1);
+                List<Diagnosis.Resource> masterAffectedResources = diagnosis.affectedResources();
+                assertThat(masterAffectedResources.size(), equalTo(1));
+                assertThat(masterAffectedResources.get(0).getType(), is(Diagnosis.Resource.Type.NODE));
+                assertThat(masterAffectedResources.get(0).getNodes().size(), is(0));
+            }
+
+            {
+                Diagnosis diagnosis = diagnosisList.get(2);
+                List<Diagnosis.Resource> nonDataNonMasterAffectedResources = diagnosis.affectedResources();
+                assertThat(nonDataNonMasterAffectedResources.size(), equalTo(1));
+                assertThat(nonDataNonMasterAffectedResources.get(0).getType(), is(Diagnosis.Resource.Type.NODE));
+                assertThat(nonDataNonMasterAffectedResources.get(0).getNodes().size(), is(0));
+            }
+        }
+
+        {
+            HealthIndicatorResult result = diskHealthIndicatorService.calculate(true, 10, healthInfo);
+            List<Diagnosis> diagnosisList = result.diagnosisList();
+            assertThat(diagnosisList.size(), equalTo(3));
+            {
+                Diagnosis diagnosis = diagnosisList.get(0);
+                List<Diagnosis.Resource> dataAffectedResources = diagnosis.affectedResources();
+                assertThat(dataAffectedResources.size(), equalTo(2));
+                assertThat(dataAffectedResources.get(0).getType(), is(Diagnosis.Resource.Type.NODE));
+                assertThat(dataAffectedResources.get(0).getNodes().size(), is(10));
+                assertThat(dataAffectedResources.get(1).getType(), is(Diagnosis.Resource.Type.INDEX));
+                assertThat(dataAffectedResources.get(1).getValues().size(), is(1));
+            }
+            {
+                Diagnosis diagnosis = diagnosisList.get(1);
+                List<Diagnosis.Resource> masterAffectedResources = diagnosis.affectedResources();
+                assertThat(masterAffectedResources.size(), equalTo(1));
+                assertThat(masterAffectedResources.get(0).getType(), is(Diagnosis.Resource.Type.NODE));
+                assertThat(masterAffectedResources.get(0).getNodes().size(), is(10));
+            }
+
+            {
+                Diagnosis diagnosis = diagnosisList.get(2);
+                List<Diagnosis.Resource> nonDataNonMasterAffectedResources = diagnosis.affectedResources();
+                assertThat(nonDataNonMasterAffectedResources.size(), equalTo(1));
+                assertThat(nonDataNonMasterAffectedResources.get(0).getType(), is(Diagnosis.Resource.Type.NODE));
+                assertThat(nonDataNonMasterAffectedResources.get(0).getNodes().size(), is(10));
+            }
+        }
+
+    }
+
     private Set<DiscoveryNode> createNodesWithAllRoles() {
     private Set<DiscoveryNode> createNodesWithAllRoles() {
         return createNodes(DiscoveryNodeRole.roles());
         return createNodes(DiscoveryNodeRole.roles());
     }
     }

+ 37 - 0
server/src/test/java/org/elasticsearch/snapshots/RepositoryIntegrityHealthIndicatorServiceTests.java

@@ -28,6 +28,7 @@ import org.elasticsearch.test.ESTestCase;
 import java.util.Collections;
 import java.util.Collections;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
+import java.util.stream.Stream;
 
 
 import static org.elasticsearch.common.util.CollectionUtils.appendToCopy;
 import static org.elasticsearch.common.util.CollectionUtils.appendToCopy;
 import static org.elasticsearch.health.HealthStatus.GREEN;
 import static org.elasticsearch.health.HealthStatus.GREEN;
@@ -115,6 +116,42 @@ public class RepositoryIntegrityHealthIndicatorServiceTests extends ESTestCase {
         );
         );
     }
     }
 
 
+    public void testLimitNumberOfAffectedResources() {
+        List<RepositoryMetadata> repos = Stream.iterate(0, n -> n + 1)
+            .limit(20)
+            .map(i -> createRepositoryMetadata("corrupted-repo" + i, true))
+            .toList();
+        var clusterState = createClusterStateWith(new RepositoriesMetadata(repos));
+        var service = createRepositoryCorruptionHealthIndicatorService(clusterState);
+
+        {
+            assertThat(
+                service.calculate(true, 10, HealthInfo.EMPTY_HEALTH_INFO).diagnosisList(),
+                equalTo(
+                    List.of(
+                        new Diagnosis(
+                            CORRUPTED_REPOSITORY,
+                            List.of(
+                                new Diagnosis.Resource(
+                                    Type.SNAPSHOT_REPOSITORY,
+                                    repos.stream().limit(10).map(RepositoryMetadata::name).toList()
+                                )
+                            )
+                        )
+                    )
+                )
+            );
+        }
+
+        {
+            assertThat(
+                service.calculate(true, 0, HealthInfo.EMPTY_HEALTH_INFO).diagnosisList(),
+                equalTo(List.of(new Diagnosis(CORRUPTED_REPOSITORY, List.of(new Diagnosis.Resource(Type.SNAPSHOT_REPOSITORY, List.of())))))
+            );
+        }
+
+    }
+
     private static ClusterState createClusterStateWith(RepositoriesMetadata metadata) {
     private static ClusterState createClusterStateWith(RepositoriesMetadata metadata) {
         var builder = ClusterState.builder(new ClusterName("test-cluster"));
         var builder = ClusterState.builder(new ClusterName("test-cluster"));
         if (metadata != null) {
         if (metadata != null) {

+ 3 - 3
x-pack/plugin/core/src/internalClusterTest/java/org/elasticsearch/xpack/cluster/routing/allocation/DataTierShardAvailabilityHealthIndicatorIT.java

@@ -70,7 +70,7 @@ public class DataTierShardAvailabilityHealthIndicatorIT extends ESIntegTestCase
         ensureYellow("test");
         ensureYellow("test");
         GetHealthAction.Response healthResponse = client().execute(
         GetHealthAction.Response healthResponse = client().execute(
             GetHealthAction.INSTANCE,
             GetHealthAction.INSTANCE,
-            new GetHealthAction.Request(ShardsAvailabilityHealthIndicatorService.NAME, true)
+            new GetHealthAction.Request(ShardsAvailabilityHealthIndicatorService.NAME, true, 1000)
         ).get();
         ).get();
         HealthIndicatorResult indicatorResult = healthResponse.findIndicator(ShardsAvailabilityHealthIndicatorService.NAME);
         HealthIndicatorResult indicatorResult = healthResponse.findIndicator(ShardsAvailabilityHealthIndicatorService.NAME);
         assertThat(indicatorResult.status(), equalTo(HealthStatus.YELLOW));
         assertThat(indicatorResult.status(), equalTo(HealthStatus.YELLOW));
@@ -107,7 +107,7 @@ public class DataTierShardAvailabilityHealthIndicatorIT extends ESIntegTestCase
         ensureYellow("test");
         ensureYellow("test");
         GetHealthAction.Response healthResponse = client().execute(
         GetHealthAction.Response healthResponse = client().execute(
             GetHealthAction.INSTANCE,
             GetHealthAction.INSTANCE,
-            new GetHealthAction.Request(ShardsAvailabilityHealthIndicatorService.NAME, true)
+            new GetHealthAction.Request(ShardsAvailabilityHealthIndicatorService.NAME, true, 1000)
         ).get();
         ).get();
         ClusterAllocationExplanation explain = client().admin()
         ClusterAllocationExplanation explain = client().admin()
             .cluster()
             .cluster()
@@ -152,7 +152,7 @@ public class DataTierShardAvailabilityHealthIndicatorIT extends ESIntegTestCase
         ensureYellow("test");
         ensureYellow("test");
         GetHealthAction.Response healthResponse = client().execute(
         GetHealthAction.Response healthResponse = client().execute(
             GetHealthAction.INSTANCE,
             GetHealthAction.INSTANCE,
-            new GetHealthAction.Request(ShardsAvailabilityHealthIndicatorService.NAME, true)
+            new GetHealthAction.Request(ShardsAvailabilityHealthIndicatorService.NAME, true, 1000)
         ).get();
         ).get();
         HealthIndicatorResult indicatorResult = healthResponse.findIndicator(ShardsAvailabilityHealthIndicatorService.NAME);
         HealthIndicatorResult indicatorResult = healthResponse.findIndicator(ShardsAvailabilityHealthIndicatorService.NAME);
         assertThat(indicatorResult.status(), equalTo(HealthStatus.YELLOW));
         assertThat(indicatorResult.status(), equalTo(HealthStatus.YELLOW));

+ 1 - 1
x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/IlmHealthIndicatorService.java

@@ -66,7 +66,7 @@ public class IlmHealthIndicatorService implements HealthIndicatorService {
     }
     }
 
 
     @Override
     @Override
-    public HealthIndicatorResult calculate(boolean verbose, HealthInfo healthInfo) {
+    public HealthIndicatorResult calculate(boolean verbose, int maxAffectedResourcesCount, HealthInfo healthInfo) {
         final ClusterState currentState = clusterService.state();
         final ClusterState currentState = clusterService.state();
         var ilmMetadata = currentState.metadata().custom(IndexLifecycleMetadata.TYPE, IndexLifecycleMetadata.EMPTY);
         var ilmMetadata = currentState.metadata().custom(IndexLifecycleMetadata.TYPE, IndexLifecycleMetadata.EMPTY);
         final OperationMode currentMode = currentILMMode(currentState);
         final OperationMode currentMode = currentILMMode(currentState);

+ 5 - 2
x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/slm/SlmHealthIndicatorService.java

@@ -99,7 +99,7 @@ public class SlmHealthIndicatorService implements HealthIndicatorService {
     }
     }
 
 
     @Override
     @Override
-    public HealthIndicatorResult calculate(boolean verbose, HealthInfo healthInfo) {
+    public HealthIndicatorResult calculate(boolean verbose, int maxAffectedResourcesCount, HealthInfo healthInfo) {
         final ClusterState currentState = clusterService.state();
         final ClusterState currentState = clusterService.state();
         var slmMetadata = currentState.metadata().custom(SnapshotLifecycleMetadata.TYPE, SnapshotLifecycleMetadata.EMPTY);
         var slmMetadata = currentState.metadata().custom(SnapshotLifecycleMetadata.TYPE, SnapshotLifecycleMetadata.EMPTY);
         final OperationMode currentMode = currentSLMMode(currentState);
         final OperationMode currentMode = currentSLMMode(currentState);
@@ -181,7 +181,10 @@ public class SlmHealthIndicatorService implements HealthIndicatorService {
                             List.of(
                             List.of(
                                 new Diagnosis.Resource(
                                 new Diagnosis.Resource(
                                     Diagnosis.Resource.Type.SLM_POLICY,
                                     Diagnosis.Resource.Type.SLM_POLICY,
-                                    unhealthyPolicies.stream().map(SnapshotLifecyclePolicyMetadata::getName).toList()
+                                    unhealthyPolicies.stream()
+                                        .map(SnapshotLifecyclePolicyMetadata::getName)
+                                        .limit(Math.min(unhealthyPolicies.size(), maxAffectedResourcesCount))
+                                        .toList()
                                 )
                                 )
                             )
                             )
                         )
                         )