This is an automated email from the ASF dual-hosted git repository.

devesh pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new 374b3e7c05 HDDS-12708. Fix Unhealthy Containers API for pagination 
(#8796)
374b3e7c05 is described below

commit 374b3e7c05b11ef577d6f344a0d33705a4f026d3
Author: Arafat2198 <arafatp...@gmail.com>
AuthorDate: Fri Jul 25 13:01:17 2025 +0530

    HDDS-12708. Fix Unhealthy Containers API for pagination (#8796)
---
 .../apache/hadoop/ozone/recon/TestReconTasks.java  | 17 ++++---
 .../recon/schema/ContainerSchemaDefinition.java    |  8 +--
 .../apache/hadoop/ozone/recon/ReconConstants.java  |  2 +
 .../ozone/recon/api/ClusterStateEndpoint.java      |  3 +-
 .../hadoop/ozone/recon/api/ContainerEndpoint.java  | 50 ++++++++++++-------
 .../api/types/UnhealthyContainersResponse.java     | 33 ++++++++++++
 .../persistence/ContainerHealthSchemaManager.java  | 58 ++++++++++++++++++----
 .../ozone/recon/api/TestContainerEndpoint.java     | 34 +++++++++----
 .../ozone/recon/fsck/TestContainerHealthTask.java  |  3 +-
 9 files changed, 156 insertions(+), 52 deletions(-)

diff --git 
a/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestReconTasks.java
 
b/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestReconTasks.java
index c4a7dbb0fb..ed0ddde04c 100644
--- 
a/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestReconTasks.java
+++ 
b/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestReconTasks.java
@@ -28,6 +28,7 @@
 import java.time.Duration;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import org.apache.hadoop.hdds.client.RatisReplicationConfig;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
@@ -169,8 +170,8 @@ public void testMissingContainerDownNode() throws Exception 
{
       List<UnhealthyContainers> allMissingContainers =
           reconContainerManager.getContainerSchemaManager()
               .getUnhealthyContainers(
-                  ContainerSchemaDefinition.UnHealthyContainerStates.MISSING,
-                  0, 1000);
+                  ContainerSchemaDefinition.UnHealthyContainerStates.MISSING, 
0L,
+                Optional.empty(), 1000);
       return (allMissingContainers.size() == 1);
     });
 
@@ -181,7 +182,7 @@ public void testMissingContainerDownNode() throws Exception 
{
           reconContainerManager.getContainerSchemaManager()
               .getUnhealthyContainers(
                   ContainerSchemaDefinition.UnHealthyContainerStates.MISSING,
-                  0, 1000);
+                  0L, Optional.empty(), 1000);
       return (allMissingContainers.isEmpty());
     });
     IOUtils.closeQuietly(client);
@@ -247,7 +248,7 @@ public void testEmptyMissingContainerDownNode() throws 
Exception {
               .getUnhealthyContainers(
                   ContainerSchemaDefinition.UnHealthyContainerStates.
                       EMPTY_MISSING,
-                  0, 1000);
+                  0L, Optional.empty(), 1000);
 
       // Check if EMPTY_MISSING containers are not added to the DB and their 
count is logged
       Map<ContainerSchemaDefinition.UnHealthyContainerStates, Map<String, 
Long>>
@@ -275,7 +276,7 @@ public void testEmptyMissingContainerDownNode() throws 
Exception {
           reconContainerManager.getContainerSchemaManager()
               .getUnhealthyContainers(
                   ContainerSchemaDefinition.UnHealthyContainerStates.MISSING,
-                  0, 1000);
+                  0L, Optional.empty(), 1000);
       return (allMissingContainers.size() == 1);
     });
 
@@ -285,7 +286,7 @@ public void testEmptyMissingContainerDownNode() throws 
Exception {
               .getUnhealthyContainers(
                   ContainerSchemaDefinition.UnHealthyContainerStates.
                       EMPTY_MISSING,
-                  0, 1000);
+                  0L, Optional.empty(), 1000);
 
 
       Map<ContainerSchemaDefinition.UnHealthyContainerStates, Map<String, 
Long>>
@@ -315,7 +316,7 @@ public void testEmptyMissingContainerDownNode() throws 
Exception {
               .getUnhealthyContainers(
                   ContainerSchemaDefinition.UnHealthyContainerStates.
                       EMPTY_MISSING,
-                  0, 1000);
+                  0L, Optional.empty(), 1000);
 
       Map<ContainerSchemaDefinition.UnHealthyContainerStates, Map<String, 
Long>>
           unhealthyContainerStateStatsMap = reconScm.getContainerHealthTask()
@@ -335,7 +336,7 @@ public void testEmptyMissingContainerDownNode() throws 
Exception {
           reconContainerManager.getContainerSchemaManager()
               .getUnhealthyContainers(
                   ContainerSchemaDefinition.UnHealthyContainerStates.MISSING,
-                  0, 1000);
+                  0L, Optional.empty(), 1000);
       return (allMissingContainers.isEmpty());
     });
 
diff --git 
a/hadoop-ozone/recon-codegen/src/main/java/org/apache/ozone/recon/schema/ContainerSchemaDefinition.java
 
b/hadoop-ozone/recon-codegen/src/main/java/org/apache/ozone/recon/schema/ContainerSchemaDefinition.java
index 5919028a56..05bf4f158d 100644
--- 
a/hadoop-ozone/recon-codegen/src/main/java/org/apache/ozone/recon/schema/ContainerSchemaDefinition.java
+++ 
b/hadoop-ozone/recon-codegen/src/main/java/org/apache/ozone/recon/schema/ContainerSchemaDefinition.java
@@ -37,11 +37,10 @@
  */
 @Singleton
 public class ContainerSchemaDefinition implements ReconSchemaDefinition {
+  private static final Logger LOG = 
LoggerFactory.getLogger(ContainerSchemaDefinition.class);
 
   public static final String UNHEALTHY_CONTAINERS_TABLE_NAME =
       "UNHEALTHY_CONTAINERS";
-  private static final Logger LOG =
-      LoggerFactory.getLogger(ContainerSchemaDefinition.class);
 
   private static final String CONTAINER_ID = "container_id";
   private static final String CONTAINER_STATE = "container_state";
@@ -78,9 +77,12 @@ private void createUnhealthyContainersTable() {
         .constraint(DSL.constraint("pk_container_id")
             .primaryKey(CONTAINER_ID, CONTAINER_STATE))
         .constraint(DSL.constraint(UNHEALTHY_CONTAINERS_TABLE_NAME + "ck1")
-            .check(field(name("container_state"))
+            .check(field(name(CONTAINER_STATE))
                 .in(UnHealthyContainerStates.values())))
         .execute();
+    dslContext.createIndex("idx_container_state")
+        .on(DSL.table(UNHEALTHY_CONTAINERS_TABLE_NAME), 
DSL.field(name(CONTAINER_STATE)))
+        .execute();
   }
 
   public DSLContext getDSLContext() {
diff --git 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconConstants.java
 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconConstants.java
index d1919f4f7b..396beb9e3f 100644
--- 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconConstants.java
+++ 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconConstants.java
@@ -46,6 +46,8 @@ public final class ReconConstants {
   public static final String RECON_QUERY_BATCH_PARAM = "batchNum";
   public static final String RECON_QUERY_PREVKEY = "prevKey";
   public static final String RECON_QUERY_START_PREFIX = "startPrefix";
+  public static final String RECON_QUERY_MAX_CONTAINER_ID = "maxContainerId";
+  public static final String RECON_QUERY_MIN_CONTAINER_ID = "minContainerId";
   public static final String RECON_OPEN_KEY_INCLUDE_NON_FSO = "includeNonFso";
   public static final String RECON_OPEN_KEY_INCLUDE_FSO = "includeFso";
   public static final String RECON_OM_INSIGHTS_DEFAULT_START_PREFIX = "/";
diff --git 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ClusterStateEndpoint.java
 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ClusterStateEndpoint.java
index e3bbf10fe1..8be9ef4746 100644
--- 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ClusterStateEndpoint.java
+++ 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ClusterStateEndpoint.java
@@ -27,6 +27,7 @@
 import static org.apache.hadoop.ozone.om.codec.OMDBDefinition.VOLUME_TABLE;
 
 import java.util.List;
+import java.util.Optional;
 import javax.inject.Inject;
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
@@ -99,7 +100,7 @@ public Response getClusterState() {
     List<UnhealthyContainers> missingContainers = containerHealthSchemaManager
         .getUnhealthyContainers(
             ContainerSchemaDefinition.UnHealthyContainerStates.MISSING,
-            0, MISSING_CONTAINER_COUNT_LIMIT);
+            0L, Optional.empty(), MISSING_CONTAINER_COUNT_LIMIT);
 
     containerStateCounts.setMissingContainerCount(
         missingContainers.size() == MISSING_CONTAINER_COUNT_LIMIT ?
diff --git 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ContainerEndpoint.java
 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ContainerEndpoint.java
index 8ccfdbf827..89db23520d 100644
--- 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ContainerEndpoint.java
+++ 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/ContainerEndpoint.java
@@ -17,13 +17,13 @@
 
 package org.apache.hadoop.ozone.recon.api;
 
-import static 
org.apache.hadoop.ozone.recon.ReconConstants.DEFAULT_BATCH_NUMBER;
 import static org.apache.hadoop.ozone.recon.ReconConstants.DEFAULT_FETCH_COUNT;
 import static 
org.apache.hadoop.ozone.recon.ReconConstants.DEFAULT_FILTER_FOR_MISSING_CONTAINERS;
 import static 
org.apache.hadoop.ozone.recon.ReconConstants.PREV_CONTAINER_ID_DEFAULT_VALUE;
-import static 
org.apache.hadoop.ozone.recon.ReconConstants.RECON_QUERY_BATCH_PARAM;
 import static org.apache.hadoop.ozone.recon.ReconConstants.RECON_QUERY_FILTER;
 import static org.apache.hadoop.ozone.recon.ReconConstants.RECON_QUERY_LIMIT;
+import static 
org.apache.hadoop.ozone.recon.ReconConstants.RECON_QUERY_MAX_CONTAINER_ID;
+import static 
org.apache.hadoop.ozone.recon.ReconConstants.RECON_QUERY_MIN_CONTAINER_ID;
 import static org.apache.hadoop.ozone.recon.ReconConstants.RECON_QUERY_PREVKEY;
 
 import java.io.IOException;
@@ -35,6 +35,7 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.UUID;
 import java.util.stream.Collectors;
 import javax.inject.Inject;
@@ -339,7 +340,7 @@ public Response getMissingContainers(
   ) {
     List<MissingContainerMetadata> missingContainers = new ArrayList<>();
     containerHealthSchemaManager.getUnhealthyContainers(
-            UnHealthyContainerStates.MISSING, 0, limit)
+            UnHealthyContainerStates.MISSING, 0L, Optional.empty(), limit)
         .forEach(container -> {
           long containerID = container.getContainerId();
           try {
@@ -373,9 +374,12 @@ public Response getMissingContainers(
    *              eg UNDER_REPLICATED, MIS_REPLICATED, OVER_REPLICATED or
    *              MISSING. Passing null returns all containers.
    * @param limit The limit of unhealthy containers to return.
-   * @param batchNum The batch number (like "page number") of results to 
return.
-   *                 Passing 1, will return records 1 to limit. 2 will return
-   *                 limit + 1 to 2 * limit, etc.
+   * @param maxContainerId Upper bound for container IDs to include 
(exclusive).
+   *                       When specified, returns containers with IDs less 
than this value
+   *                       in descending order. Use for backward pagination.
+   * @param minContainerId Lower bound for container IDs to include 
(exclusive).
+   *                       When maxContainerId is not specified, returns 
containers with IDs
+   *                       greater than this value in ascending order. Use for 
forward pagination.
    * @return {@link Response}
    */
   @GET
@@ -384,10 +388,11 @@ public Response getUnhealthyContainers(
       @PathParam("state") String state,
       @DefaultValue(DEFAULT_FETCH_COUNT) @QueryParam(RECON_QUERY_LIMIT)
       int limit,
-      @DefaultValue(DEFAULT_BATCH_NUMBER)
-      @QueryParam(RECON_QUERY_BATCH_PARAM) int batchNum) {
-    int offset = Math.max(((batchNum - 1) * limit), 0);
-
+      @DefaultValue(PREV_CONTAINER_ID_DEFAULT_VALUE)
+      @QueryParam(RECON_QUERY_MAX_CONTAINER_ID) long maxContainerId,
+      @DefaultValue(PREV_CONTAINER_ID_DEFAULT_VALUE)
+      @QueryParam(RECON_QUERY_MIN_CONTAINER_ID) long minContainerId) {
+    Optional<Long> maxContainerIdOpt = maxContainerId > 0 ? 
Optional.of(maxContainerId) : Optional.empty();
     List<UnhealthyContainerMetadata> unhealthyMeta = new ArrayList<>();
     List<UnhealthyContainersSummary> summary;
     try {
@@ -401,7 +406,7 @@ public Response getUnhealthyContainers(
 
       summary = containerHealthSchemaManager.getUnhealthyContainersSummary();
       List<UnhealthyContainers> containers = containerHealthSchemaManager
-          .getUnhealthyContainers(internalState, offset, limit);
+          .getUnhealthyContainers(internalState, minContainerId, 
maxContainerIdOpt, limit);
 
       // Filtering out EMPTY_MISSING and NEGATIVE_SIZE containers from the 
response.
       // These container states are not being inserted into the database as 
they represent
@@ -434,6 +439,12 @@ public Response getUnhealthyContainers(
 
     UnhealthyContainersResponse response =
         new UnhealthyContainersResponse(unhealthyMeta);
+    if (!unhealthyMeta.isEmpty()) {
+      
response.setFirstKey(unhealthyMeta.stream().map(UnhealthyContainerMetadata::getContainerID)
+          .min(Long::compareTo).orElse(0L));
+      
response.setLastKey(unhealthyMeta.stream().map(UnhealthyContainerMetadata::getContainerID)
+          .max(Long::compareTo).orElse(0L));
+    }
     for (UnhealthyContainersSummary s : summary) {
       response.setSummaryCount(s.getContainerState(), s.getCount());
     }
@@ -445,9 +456,12 @@ public Response getUnhealthyContainers(
    * {@link org.apache.hadoop.ozone.recon.api.types.UnhealthyContainerMetadata}
    * for all unhealthy containers.
    * @param limit The limit of unhealthy containers to return.
-   * @param batchNum The batch number (like "page number") of results to 
return.
-   *                 Passing 1, will return records 1 to limit. 2 will return
-   *                 limit + 1 to 2 * limit, etc.
+   * @param maxContainerId Upper bound for container IDs to include 
(exclusive).
+   *                       When specified, returns containers with IDs less 
than this value
+   *                       in descending order. Use for backward pagination.
+   * @param minContainerId Lower bound for container IDs to include 
(exclusive).
+   *                       When maxContainerId is not specified, returns 
containers with IDs
+   *                       greater than this value in ascending order. Use for 
forward pagination.
    * @return {@link Response}
    */
   @GET
@@ -455,9 +469,11 @@ public Response getUnhealthyContainers(
   public Response getUnhealthyContainers(
       @DefaultValue(DEFAULT_FETCH_COUNT) @QueryParam(RECON_QUERY_LIMIT)
       int limit,
-      @DefaultValue(DEFAULT_BATCH_NUMBER)
-      @QueryParam(RECON_QUERY_BATCH_PARAM) int batchNum) {
-    return getUnhealthyContainers(null, limit, batchNum);
+      @DefaultValue(PREV_CONTAINER_ID_DEFAULT_VALUE)
+      @QueryParam(RECON_QUERY_MAX_CONTAINER_ID) long maxContainerId,
+      @DefaultValue(PREV_CONTAINER_ID_DEFAULT_VALUE)
+      @QueryParam(RECON_QUERY_MIN_CONTAINER_ID) long minContainerId) {
+    return getUnhealthyContainers(null, limit, maxContainerId, minContainerId);
   }
 
   /**
diff --git 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/UnhealthyContainersResponse.java
 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/UnhealthyContainersResponse.java
index 07c1ca6819..350f9e8ced 100644
--- 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/UnhealthyContainersResponse.java
+++ 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/types/UnhealthyContainersResponse.java
@@ -55,6 +55,23 @@ public class UnhealthyContainersResponse {
   @JsonProperty("replicaMismatchCount")
   private long replicaMismatchCount = 0;
 
+  /**
+   * The smallest container ID in the current response batch.
+   * Used for pagination to determine the lower bound for the next page.
+   */
+  @JsonProperty("firstKey")
+  private long firstKey = 0;
+
+
+  /**
+   * The largest container ID in the current response batch.
+   * Used for pagination to determine the upper bound for the next page.
+   */
+  @JsonProperty("lastKey")
+  private long lastKey = 0;
+
+
+
   /**
    * A collection of unhealthy containers.
    */
@@ -108,7 +125,23 @@ public long getReplicaMismatchCount() {
     return replicaMismatchCount;
   }
 
+  public long getLastKey() {
+    return lastKey;
+  }
+
+  public long getFirstKey() {
+    return firstKey;
+  }
+
   public Collection<UnhealthyContainerMetadata> getContainers() {
     return containers;
   }
+
+  public void setFirstKey(long firstKey) {
+    this.firstKey = firstKey;
+  }
+
+  public void setLastKey(long lastKey) {
+    this.lastKey = lastKey;
+  }
 }
diff --git 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/persistence/ContainerHealthSchemaManager.java
 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/persistence/ContainerHealthSchemaManager.java
index 84222fc6cc..b7b98ba1f9 100644
--- 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/persistence/ContainerHealthSchemaManager.java
+++ 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/persistence/ContainerHealthSchemaManager.java
@@ -23,18 +23,24 @@
 import static 
org.apache.ozone.recon.schema.generated.tables.UnhealthyContainersTable.UNHEALTHY_CONTAINERS;
 import static org.jooq.impl.DSL.count;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.sql.Connection;
+import java.util.Comparator;
 import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
 import org.apache.hadoop.ozone.recon.api.types.UnhealthyContainersSummary;
 import org.apache.ozone.recon.schema.ContainerSchemaDefinition;
 import 
org.apache.ozone.recon.schema.ContainerSchemaDefinition.UnHealthyContainerStates;
 import 
org.apache.ozone.recon.schema.generated.tables.daos.UnhealthyContainersDao;
 import 
org.apache.ozone.recon.schema.generated.tables.pojos.UnhealthyContainers;
 import 
org.apache.ozone.recon.schema.generated.tables.records.UnhealthyContainersRecord;
+import org.jooq.Condition;
 import org.jooq.Cursor;
 import org.jooq.DSLContext;
+import org.jooq.OrderField;
 import org.jooq.Record;
 import org.jooq.SelectQuery;
 import org.jooq.exception.DataAccessException;
@@ -67,32 +73,47 @@ public ContainerHealthSchemaManager(
    * matching the given state will be returned.
    * @param state Return only containers in this state, or all containers if
    *              null
-   * @param offset The starting record to return in the result set. The first
-   *               record is at zero.
+   * @param minContainerId minimum containerId for filter
+   * @param maxContainerId maximum containerId for filter
    * @param limit The total records to return
    * @return List of unhealthy containers.
    */
   public List<UnhealthyContainers> getUnhealthyContainers(
-      UnHealthyContainerStates state, int offset, int limit) {
+      UnHealthyContainerStates state, Long minContainerId, Optional<Long> 
maxContainerId, int limit) {
     DSLContext dslContext = containerSchemaDefinition.getDSLContext();
     SelectQuery<Record> query = dslContext.selectQuery();
     query.addFrom(UNHEALTHY_CONTAINERS);
+    Condition containerCondition;
+    OrderField[] orderField;
+    if (maxContainerId.isPresent() && maxContainerId.get() > 0) {
+      containerCondition = 
UNHEALTHY_CONTAINERS.CONTAINER_ID.lessThan(maxContainerId.get());
+      orderField = new OrderField[]{UNHEALTHY_CONTAINERS.CONTAINER_ID.desc(),
+          UNHEALTHY_CONTAINERS.CONTAINER_STATE.asc()};
+    } else {
+      containerCondition = 
UNHEALTHY_CONTAINERS.CONTAINER_ID.greaterThan(minContainerId);
+      orderField = new OrderField[]{UNHEALTHY_CONTAINERS.CONTAINER_ID.asc(),
+          UNHEALTHY_CONTAINERS.CONTAINER_STATE.asc()};
+    }
     if (state != null) {
       if (state.equals(ALL_REPLICAS_BAD)) {
-        query.addConditions(UNHEALTHY_CONTAINERS.CONTAINER_STATE
-            .eq(UNDER_REPLICATED.toString()));
+        
query.addConditions(containerCondition.and(UNHEALTHY_CONTAINERS.CONTAINER_STATE
+            .eq(UNDER_REPLICATED.toString())));
         query.addConditions(UNHEALTHY_CONTAINERS.ACTUAL_REPLICA_COUNT.eq(0));
       } else {
-        query.addConditions(
-            UNHEALTHY_CONTAINERS.CONTAINER_STATE.eq(state.toString()));
+        
query.addConditions(containerCondition.and(UNHEALTHY_CONTAINERS.CONTAINER_STATE.eq(state.toString())));
       }
+    } else {
+      // CRITICAL FIX: Apply pagination condition even when state is null
+      // This ensures proper pagination for the "get all unhealthy containers" 
use case
+      query.addConditions(containerCondition);
     }
-    query.addOrderBy(UNHEALTHY_CONTAINERS.CONTAINER_ID.asc(),
-        UNHEALTHY_CONTAINERS.CONTAINER_STATE.asc());
-    query.addOffset(offset);
+
+    query.addOrderBy(orderField);
     query.addLimit(limit);
 
-    return query.fetchInto(UnhealthyContainers.class);
+    return query.fetchInto(UnhealthyContainers.class).stream()
+      .sorted(Comparator.comparingLong(UnhealthyContainers::getContainerId))
+      .collect(Collectors.toList());
   }
 
   /**
@@ -151,4 +172,19 @@ public void 
insertUnhealthyContainerRecords(List<UnhealthyContainers> recs) {
     }
   }
 
+  /**
+   * Clear all unhealthy container records. This is primarily used for testing
+   * to ensure clean state between tests.
+   */
+  @VisibleForTesting
+  public void clearAllUnhealthyContainerRecords() {
+    DSLContext dslContext = containerSchemaDefinition.getDSLContext();
+    try {
+      dslContext.deleteFrom(UNHEALTHY_CONTAINERS).execute();
+      LOG.info("Cleared all unhealthy container records");
+    } catch (Exception e) {
+      LOG.info("Failed to clear unhealthy container records", e);
+    }
+  }
+
 }
diff --git 
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestContainerEndpoint.java
 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestContainerEndpoint.java
index 72376c7f92..a94ba618be 100644
--- 
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestContainerEndpoint.java
+++ 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/api/TestContainerEndpoint.java
@@ -804,7 +804,7 @@ void putContainerInfos(int num) throws IOException, 
TimeoutException {
 
   @Test
   public void testUnhealthyContainers() throws IOException, TimeoutException {
-    Response response = containerEndpoint.getUnhealthyContainers(1000, 1);
+    Response response = containerEndpoint.getUnhealthyContainers(1000, 0, 0);
 
     UnhealthyContainersResponse responseObject =
         (UnhealthyContainersResponse) response.getEntity();
@@ -823,7 +823,7 @@ public void testUnhealthyContainers() throws IOException, 
TimeoutException {
     uuid4 = newDatanode("host4", "127.0.0.4");
     createUnhealthyRecords(5, 4, 3, 2, 1);
 
-    response = containerEndpoint.getUnhealthyContainers(1000, 1);
+    response = containerEndpoint.getUnhealthyContainers(1000, 0, 0);
 
     responseObject = (UnhealthyContainersResponse) response.getEntity();
     assertEquals(5, responseObject.getMissingCount());
@@ -925,7 +925,7 @@ public void testUnhealthyContainersFilteredResponse()
 
     // Initial empty response verification
     Response response = containerEndpoint
-        .getUnhealthyContainers(missing, 1000, 1);
+        .getUnhealthyContainers(missing, 1000, 0, 0);
 
     UnhealthyContainersResponse responseObject =
         (UnhealthyContainersResponse) response.getEntity();
@@ -947,7 +947,7 @@ public void testUnhealthyContainersFilteredResponse()
     createNegativeSizeUnhealthyRecords(2); // For NEGATIVE_SIZE state
 
     // Check for unhealthy containers
-    response = containerEndpoint.getUnhealthyContainers(missing, 1000, 1);
+    response = containerEndpoint.getUnhealthyContainers(missing, 1000, 0, 0);
 
     responseObject = (UnhealthyContainersResponse) response.getEntity();
 
@@ -972,14 +972,14 @@ public void testUnhealthyContainersFilteredResponse()
 
     // Check for empty missing containers, should return zero
     Response filteredEmptyMissingResponse = containerEndpoint
-        .getUnhealthyContainers(emptyMissing, 1000, 1);
+        .getUnhealthyContainers(emptyMissing, 1000, 0, 0);
     responseObject = (UnhealthyContainersResponse) 
filteredEmptyMissingResponse.getEntity();
     records = responseObject.getContainers();
     assertEquals(0, records.size());
 
     // Check for negative size containers, should return zero
     Response filteredNegativeSizeResponse = containerEndpoint
-        .getUnhealthyContainers(negativeSize, 1000, 1);
+        .getUnhealthyContainers(negativeSize, 1000, 0, 0);
     responseObject = (UnhealthyContainersResponse) 
filteredNegativeSizeResponse.getEntity();
     records = responseObject.getContainers();
     assertEquals(0, records.size());
@@ -988,30 +988,43 @@ public void testUnhealthyContainersFilteredResponse()
   @Test
   public void testUnhealthyContainersInvalidState() {
     WebApplicationException e = assertThrows(WebApplicationException.class,
-        () -> containerEndpoint.getUnhealthyContainers("invalid", 1000, 1));
+        () -> containerEndpoint.getUnhealthyContainers("invalid", 1000, 0, 0));
     assertEquals("HTTP 400 Bad Request", e.getMessage());
   }
 
   @Test
   public void testUnhealthyContainersPaging()
       throws IOException, TimeoutException {
-    putContainerInfos(6);
+
+    // Clear any existing unhealthy container records from previous tests
+    containerHealthSchemaManager.clearAllUnhealthyContainerRecords();
+    // Create containers for all IDs that will be used in unhealthy records
+    // createUnhealthyRecords(5, 4, 3, 2) will create records for containers 
1-14
+    // So we need to create 14 containers instead of just 6
+    putContainerInfos(14);  // Changed from 6 to 14
     uuid1 = newDatanode("host1", "127.0.0.1");
     uuid2 = newDatanode("host2", "127.0.0.2");
     uuid3 = newDatanode("host3", "127.0.0.3");
     uuid4 = newDatanode("host4", "127.0.0.4");
     createUnhealthyRecords(5, 4, 3, 2, 0);
+
+    // Get first batch with no pagination (prevStartKey=0, prevLastKey=0)
     UnhealthyContainersResponse firstBatch =
         (UnhealthyContainersResponse) containerEndpoint.getUnhealthyContainers(
-            3, 1).getEntity();
+            3, 0, 0).getEntity();
     assertTrue(firstBatch.getContainers().stream()
         .flatMap(containerMetadata -> containerMetadata.getReplicas().stream()
             .map(ContainerHistory::getState))
         .allMatch(s -> s.equals("UNHEALTHY")));
 
+    // For pagination, use the last container ID from the first batch as 
prevLastKey
+    long lastContainerIdFromFirstBatch = firstBatch.getContainers().stream()
+        .map(i -> i.getContainerID()).max(Long::compareTo).get();
+
+    // Get second batch using correct pagination parameters
     UnhealthyContainersResponse secondBatch =
         (UnhealthyContainersResponse) containerEndpoint.getUnhealthyContainers(
-            3, 2).getEntity();
+            3, 0, lastContainerIdFromFirstBatch).getEntity();
 
     ArrayList<UnhealthyContainerMetadata> records
         = new ArrayList<>(firstBatch.getContainers());
@@ -1019,7 +1032,6 @@ public void testUnhealthyContainersPaging()
     assertEquals(1L, records.get(0).getContainerID());
     assertEquals(2L, records.get(1).getContainerID());
     assertEquals(3L, records.get(2).getContainerID());
-
     records
         = new ArrayList<>(secondBatch.getContainers());
     assertEquals(3, records.size());
diff --git 
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/fsck/TestContainerHealthTask.java
 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/fsck/TestContainerHealthTask.java
index fc659b49fd..ca902ea378 100644
--- 
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/fsck/TestContainerHealthTask.java
+++ 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/fsck/TestContainerHealthTask.java
@@ -41,6 +41,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.UUID;
 import org.apache.hadoop.hdds.client.RatisReplicationConfig;
@@ -225,7 +226,7 @@ public void testRun() throws Exception {
 
     List<UnhealthyContainers> unhealthyContainers =
         containerHealthSchemaManager.getUnhealthyContainers(
-            ALL_REPLICAS_BAD, 0, Integer.MAX_VALUE);
+            ALL_REPLICAS_BAD, 0L, Optional.empty(), Integer.MAX_VALUE);
     assertEquals(1, unhealthyContainers.size());
     assertEquals(2L,
         unhealthyContainers.get(0).getContainerId().longValue());


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@ozone.apache.org
For additional commands, e-mail: commits-h...@ozone.apache.org

Reply via email to