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

ritesh pushed a commit to branch HDDS-10239-container-reconciliation
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to 
refs/heads/HDDS-10239-container-reconciliation by this push:
     new f47b407646 HDDS-11887. Recon - Identify container replicas difference 
based on content checksums (#7942)
f47b407646 is described below

commit f47b4076464abb87b49ab15e0b732687c710d4d4
Author: Zita Dombi <[email protected]>
AuthorDate: Mon Apr 7 19:34:25 2025 +0200

    HDDS-11887. Recon - Identify container replicas difference based on content 
checksums (#7942)
---
 .../interface-client/src/main/proto/hdds.proto     |  1 +
 .../recon/schema/ContainerSchemaDefinition.java    |  3 +-
 .../api/types/UnhealthyContainersResponse.java     | 13 +++
 .../ozone/recon/fsck/ContainerHealthStatus.java    | 15 +++-
 .../ozone/recon/fsck/ContainerHealthTask.java      | 34 +++++++-
 .../ozone/recon/persistence/ContainerHistory.java  | 13 ++-
 .../ozone/recon/scm/ContainerReplicaHistory.java   | 16 +++-
 .../ozone/recon/scm/ReconContainerManager.java     | 18 ++--
 .../ozone/recon/upgrade/ReconLayoutFeature.java    |  3 +-
 .../UnhealthyContainerReplicaMismatchAction.java   | 99 ++++++++++++++++++++++
 .../ozone/recon/api/TestContainerEndpoint.java     | 74 +++++++++++-----
 .../recon/fsck/TestContainerHealthStatus.java      | 51 +++++++++++
 .../ozone/recon/fsck/TestContainerHealthTask.java  | 42 ++++++++-
 .../TestContainerHealthTaskRecordGenerator.java    | 51 +++++++++++
 .../ozone/recon/scm/TestReconContainerManager.java |  6 +-
 15 files changed, 391 insertions(+), 48 deletions(-)

diff --git a/hadoop-hdds/interface-client/src/main/proto/hdds.proto 
b/hadoop-hdds/interface-client/src/main/proto/hdds.proto
index 6e78401e14..224a6550cf 100644
--- a/hadoop-hdds/interface-client/src/main/proto/hdds.proto
+++ b/hadoop-hdds/interface-client/src/main/proto/hdds.proto
@@ -437,6 +437,7 @@ message ContainerReplicaHistoryProto {
     required int64 lastSeenTime = 3;
     required int64 bcsId = 4;
     optional string state = 5;
+    optional int64 dataChecksum = 6;
 }
 
 message SCMContainerReplicaProto {
diff --git 
a/hadoop-ozone/recon-codegen/src/main/java/org/hadoop/ozone/recon/schema/ContainerSchemaDefinition.java
 
b/hadoop-ozone/recon-codegen/src/main/java/org/hadoop/ozone/recon/schema/ContainerSchemaDefinition.java
index 21f7605e53..695b144c80 100644
--- 
a/hadoop-ozone/recon-codegen/src/main/java/org/hadoop/ozone/recon/schema/ContainerSchemaDefinition.java
+++ 
b/hadoop-ozone/recon-codegen/src/main/java/org/hadoop/ozone/recon/schema/ContainerSchemaDefinition.java
@@ -50,7 +50,8 @@ public enum UnHealthyContainerStates {
     OVER_REPLICATED,
     MIS_REPLICATED,
     ALL_REPLICAS_BAD,
-    NEGATIVE_SIZE // Added new state to track containers with negative sizes
+    NEGATIVE_SIZE, // Added new state to track containers with negative sizes
+    REPLICA_MISMATCH
   }
 
   private static final String CONTAINER_ID = "container_id";
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 8b97ab7660..92a8f90d89 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
@@ -49,6 +49,12 @@ public class UnhealthyContainersResponse {
   @JsonProperty("misReplicatedCount")
   private long misReplicatedCount = 0;
 
+  /**
+   * Total count of containers that have replicas with mismatched data 
checksums.
+   */
+  @JsonProperty("replicaMismatchCount")
+  private long replicaMismatchCount = 0;
+
   /**
    * A collection of unhealthy containers.
    */
@@ -76,6 +82,9 @@ public void setSummaryCount(String state, long count) {
     } else if (state.equals(
         UnHealthyContainerStates.MIS_REPLICATED.toString())) {
       this.misReplicatedCount = count;
+    } else if (state.equals(
+        UnHealthyContainerStates.REPLICA_MISMATCH.toString())) {
+      this.replicaMismatchCount = count;
     }
   }
 
@@ -95,6 +104,10 @@ public long getMisReplicatedCount() {
     return misReplicatedCount;
   }
 
+  public long getReplicaMismatchCount() {
+    return replicaMismatchCount;
+  }
+
   public Collection<UnhealthyContainerMetadata> getContainers() {
     return containers;
   }
diff --git 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/fsck/ContainerHealthStatus.java
 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/fsck/ContainerHealthStatus.java
index 269240c969..4c28806dfa 100644
--- 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/fsck/ContainerHealthStatus.java
+++ 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/fsck/ContainerHealthStatus.java
@@ -45,6 +45,7 @@ public class ContainerHealthStatus {
 
   private final ContainerInfo container;
   private final int replicaDelta;
+  private final Set<ContainerReplica> replicas;
   private final Set<ContainerReplica> healthyReplicas;
   private final Set<ContainerReplica> healthyAvailReplicas;
   private final ContainerPlacementStatus placementStatus;
@@ -62,6 +63,7 @@ public class ContainerHealthStatus {
     this.reconContainerMetadataManager = reconContainerMetadataManager;
     this.container = container;
     int repFactor = container.getReplicationConfig().getRequiredNodes();
+    this.replicas = replicas;
     this.healthyReplicas = replicas
         .stream()
         .filter(r -> !r.getState()
@@ -158,6 +160,13 @@ public boolean isEmpty() {
     return numKeys == 0;
   }
 
+  public boolean isDataChecksumMismatched() {
+    return !replicas.isEmpty() && replicas.stream()
+            .map(ContainerReplica::getDataChecksum)
+            .distinct()
+            .count() != 1;
+  }
+
   private ContainerPlacementStatus getPlacementStatus(
       PlacementPolicy policy, int repFactor) {
     List<DatanodeDetails> dns = healthyReplicas.stream()
@@ -180,19 +189,19 @@ public long getNumKeys() {
   }
 
   private ContainerReplicaCount getContainerReplicaCountInstance(
-      OzoneConfiguration conf, Set<ContainerReplica> replicas) {
+      OzoneConfiguration conf, Set<ContainerReplica> containerReplicas) {
     ReplicationManager.ReplicationManagerConfiguration rmConf = conf.getObject(
         ReplicationManager.ReplicationManagerConfiguration.class);
     boolean isEC = container.getReplicationConfig()
                        .getReplicationType() == HddsProtos.ReplicationType.EC;
     return isEC ?
                new ECContainerReplicaCount(container,
-                   replicas, new ArrayList<>(),
+                   containerReplicas, new ArrayList<>(),
                    rmConf.getMaintenanceRemainingRedundancy()) :
                // This class ignores unhealthy replicas,
                // therefore set 'considerUnhealthy' to false.
                new RatisContainerReplicaCount(container,
-                   replicas, new ArrayList<>(),
+                   containerReplicas, new ArrayList<>(),
                    rmConf.getMaintenanceReplicaMinimum(), false);
   }
 }
diff --git 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/fsck/ContainerHealthTask.java
 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/fsck/ContainerHealthTask.java
index 807b7e1f31..8545951f92 100644
--- 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/fsck/ContainerHealthTask.java
+++ 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/fsck/ContainerHealthTask.java
@@ -114,7 +114,7 @@ public void run() {
         Thread.sleep(interval);
       }
     } catch (Throwable t) {
-      LOG.error("Exception in Missing Container task Thread.", t);
+      LOG.error("Exception in Container Health task thread.", t);
       if (t instanceof InterruptedException) {
         Thread.currentThread().interrupt();
       }
@@ -231,6 +231,8 @@ private void initializeUnhealthyContainerStateStatsMap(
         UnHealthyContainerStates.MIS_REPLICATED, new HashMap<>());
     unhealthyContainerStateStatsMap.put(
         UnHealthyContainerStates.NEGATIVE_SIZE, new HashMap<>());
+    unhealthyContainerStateStatsMap.put(
+            UnHealthyContainerStates.REPLICA_MISMATCH, new HashMap<>());
   }
 
   private ContainerHealthStatus setCurrentContainer(long recordId)
@@ -349,7 +351,7 @@ private void processContainer(ContainerInfo container, long 
currentTime,
           containerReplicas, placementPolicy,
           reconContainerMetadataManager, conf);
 
-      if (h.isHealthilyReplicated() || h.isDeleted()) {
+      if ((h.isHealthilyReplicated() && !h.isDataChecksumMismatched()) || 
h.isDeleted()) {
         return;
       }
       // For containers deleted in SCM, we sync the container state here.
@@ -483,7 +485,7 @@ public static class ContainerHealthRecords {
      */
     public static boolean retainOrUpdateRecord(
         ContainerHealthStatus container, UnhealthyContainersRecord rec) {
-      boolean returnValue = false;
+      boolean returnValue;
       switch (UnHealthyContainerStates.valueOf(rec.getContainerState())) {
       case MISSING:
         returnValue = container.isMissing() && !container.isEmpty();
@@ -497,6 +499,9 @@ public static boolean retainOrUpdateRecord(
       case OVER_REPLICATED:
         returnValue = keepOverReplicatedRecord(container, rec);
         break;
+      case REPLICA_MISMATCH:
+        returnValue = keepReplicaMismatchRecord(container, rec);
+        break;
       default:
         returnValue = false;
       }
@@ -525,7 +530,7 @@ public static List<UnhealthyContainers> 
generateUnhealthyRecords(
         Map<UnHealthyContainerStates, Map<String, Long>>
             unhealthyContainerStateStatsMap) {
       List<UnhealthyContainers> records = new ArrayList<>();
-      if (container.isHealthilyReplicated() || container.isDeleted()) {
+      if ((container.isHealthilyReplicated() && 
!container.isDataChecksumMismatched()) || container.isDeleted()) {
         return records;
       }
 
@@ -590,6 +595,16 @@ public static List<UnhealthyContainers> 
generateUnhealthyRecords(
             unhealthyContainerStateStatsMap);
       }
 
+      if (container.isDataChecksumMismatched()
+              && !recordForStateExists.contains(
+              UnHealthyContainerStates.REPLICA_MISMATCH.toString())) {
+        records.add(recordForState(
+                container, UnHealthyContainerStates.REPLICA_MISMATCH, time));
+        populateContainerStats(container,
+                UnHealthyContainerStates.REPLICA_MISMATCH,
+                unhealthyContainerStateStatsMap);
+      }
+
       return records;
     }
 
@@ -647,6 +662,17 @@ private static boolean keepMisReplicatedRecord(
       return false;
     }
 
+    private static boolean keepReplicaMismatchRecord(
+            ContainerHealthStatus container, UnhealthyContainersRecord rec) {
+      if (container.isDataChecksumMismatched()) {
+        updateExpectedReplicaCount(rec, container.getReplicationFactor());
+        updateActualReplicaCount(rec, container.getReplicaCount());
+        updateReplicaDelta(rec, container.replicaDelta());
+        return true;
+      }
+      return false;
+    }
+
     /**
      * With a Jooq record, if you update any field in the record, the record
      * is marked as changed, even if you updated it to the same value as it is
diff --git 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/persistence/ContainerHistory.java
 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/persistence/ContainerHistory.java
index 5d2895da25..2294e1cfab 100644
--- 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/persistence/ContainerHistory.java
+++ 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/persistence/ContainerHistory.java
@@ -31,10 +31,12 @@ public class ContainerHistory implements Serializable {
   private long lastSeenTime;
   private long lastBcsId;
   private String state;
+  private long dataChecksum;
 
+  @SuppressWarnings("parameternumber")
   public ContainerHistory(long containerId, String datanodeUuid,
                           String datanodeHost, long firstSeenTime,
-                          long lastSeenTime, long lastBcsId, String state) {
+                          long lastSeenTime, long lastBcsId, String state, 
long dataChecksum) {
     this.containerId = containerId;
     this.datanodeUuid = datanodeUuid;
     this.datanodeHost = datanodeHost;
@@ -42,6 +44,7 @@ public ContainerHistory(long containerId, String datanodeUuid,
     this.lastSeenTime = lastSeenTime;
     this.lastBcsId = lastBcsId;
     this.state = state;
+    this.dataChecksum = dataChecksum;
   }
 
   // Default constructor, used by jackson lib for object deserialization.
@@ -99,4 +102,12 @@ public String getState() {
   public void setState(String state) {
     this.state = state;
   }
+
+  public long getDataChecksum() {
+    return dataChecksum;
+  }
+
+  public void setDataChecksum(long dataChecksum) {
+    this.dataChecksum = dataChecksum;
+  }
 }
diff --git 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ContainerReplicaHistory.java
 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ContainerReplicaHistory.java
index 2295727416..d47c7010a2 100644
--- 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ContainerReplicaHistory.java
+++ 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ContainerReplicaHistory.java
@@ -39,14 +39,16 @@ public class ContainerReplicaHistory {
 
   private long bcsId;
   private String state;
+  private long dataChecksum;
 
   public ContainerReplicaHistory(UUID id, Long firstSeenTime,
-      Long lastSeenTime, long bcsId, String state) {
+      Long lastSeenTime, long bcsId, String state, long dataChecksum) {
     this.uuid = id;
     this.firstSeenTime = firstSeenTime;
     this.lastSeenTime = lastSeenTime;
     this.bcsId = bcsId;
     this.state = state;
+    this.dataChecksum = dataChecksum;
   }
 
   public long getBcsId() {
@@ -81,16 +83,24 @@ public void setState(String state) {
     this.state = state;
   }
 
+  public long getDataChecksum() {
+    return dataChecksum;
+  }
+
+  public void setDataChecksum(long dataChecksum) {
+    this.dataChecksum = dataChecksum;
+  }
+
   public static ContainerReplicaHistory fromProto(
       ContainerReplicaHistoryProto proto) {
     return new ContainerReplicaHistory(UUID.fromString(proto.getUuid()),
         proto.getFirstSeenTime(), proto.getLastSeenTime(), proto.getBcsId(),
-        proto.getState());
+        proto.getState(), proto.getDataChecksum());
   }
 
   public ContainerReplicaHistoryProto toProto() {
     return ContainerReplicaHistoryProto.newBuilder().setUuid(uuid.toString())
         .setFirstSeenTime(firstSeenTime).setLastSeenTime(lastSeenTime)
-        .setBcsId(bcsId).setState(state).build();
+        .setBcsId(bcsId).setState(state).setDataChecksum(dataChecksum).build();
   }
 }
diff --git 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconContainerManager.java
 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconContainerManager.java
index fa3e2d1957..b277178b71 100644
--- 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconContainerManager.java
+++ 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconContainerManager.java
@@ -277,6 +277,7 @@ public void updateContainerReplica(ContainerID containerID,
     boolean flushToDB = false;
     long bcsId = replica.getSequenceId() != null ? replica.getSequenceId() : 
-1;
     String state = replica.getState().toString();
+    long dataChecksum = replica.getDataChecksum();
 
     // If replica doesn't exist in in-memory map, add to DB and add to map
     if (replicaLastSeenMap == null) {
@@ -284,7 +285,7 @@ public void updateContainerReplica(ContainerID containerID,
       replicaHistoryMap.putIfAbsent(id,
           new ConcurrentHashMap<UUID, ContainerReplicaHistory>() {{
             put(uuid, new ContainerReplicaHistory(uuid, currTime, currTime,
-                bcsId, state));
+                bcsId, state, dataChecksum));
           }});
       flushToDB = true;
     } else {
@@ -294,7 +295,7 @@ public void updateContainerReplica(ContainerID containerID,
         // New Datanode
         replicaLastSeenMap.put(uuid,
             new ContainerReplicaHistory(uuid, currTime, currTime, bcsId,
-                state));
+                state, dataChecksum));
         flushToDB = true;
       } else {
         // if the object exists, only update the last seen time & bcsId fields
@@ -305,7 +306,7 @@ public void updateContainerReplica(ContainerID containerID,
     }
 
     if (flushToDB) {
-      upsertContainerHistory(id, uuid, currTime, bcsId, state);
+      upsertContainerHistory(id, uuid, currTime, bcsId, state, dataChecksum);
     }
   }
 
@@ -322,6 +323,7 @@ public void removeContainerReplica(ContainerID containerID,
     final DatanodeDetails dnInfo = replica.getDatanodeDetails();
     final UUID uuid = dnInfo.getUuid();
     String state = replica.getState().toString();
+    long dataChecksum = replica.getDataChecksum();
 
     final Map<UUID, ContainerReplicaHistory> replicaLastSeenMap =
         replicaHistoryMap.get(id);
@@ -330,7 +332,7 @@ public void removeContainerReplica(ContainerID containerID,
       if (ts != null) {
         // Flush to DB, then remove from in-memory map
         upsertContainerHistory(id, uuid, ts.getLastSeenTime(), ts.getBcsId(),
-            state);
+            state, dataChecksum);
         replicaLastSeenMap.remove(uuid);
       }
     }
@@ -389,9 +391,10 @@ public List<ContainerHistory> getAllContainerHistory(long 
containerID) {
       final long lastSeenTime = entry.getValue().getLastSeenTime();
       long bcsId = entry.getValue().getBcsId();
       String state = entry.getValue().getState();
+      long dataChecksum = entry.getValue().getDataChecksum();
 
       resList.add(new ContainerHistory(containerID, uuid.toString(), hostname,
-          firstSeenTime, lastSeenTime, bcsId, state));
+          firstSeenTime, lastSeenTime, bcsId, state, dataChecksum));
     }
     return resList;
   }
@@ -426,7 +429,7 @@ public void flushReplicaHistoryMapToDB(boolean clearMap) {
   }
 
   public void upsertContainerHistory(long containerID, UUID uuid, long time,
-                                     long bcsId, String state) {
+                                     long bcsId, String state, long 
dataChecksum) {
     Map<UUID, ContainerReplicaHistory> tsMap;
     try {
       tsMap = cdbServiceProvider.getContainerReplicaHistory(containerID);
@@ -434,11 +437,12 @@ public void upsertContainerHistory(long containerID, UUID 
uuid, long time,
       if (ts == null) {
         // New entry
         tsMap.put(uuid, new ContainerReplicaHistory(uuid, time, time, bcsId,
-            state));
+            state, dataChecksum));
       } else {
         // Entry exists, update last seen time and put it back to DB.
         ts.setLastSeenTime(time);
         ts.setState(state);
+        ts.setDataChecksum(dataChecksum);
       }
       cdbServiceProvider.storeContainerReplicaHistory(containerID, tsMap);
     } catch (IOException e) {
diff --git 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconLayoutFeature.java
 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconLayoutFeature.java
index da4f9d9d30..2b4569d449 100644
--- 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconLayoutFeature.java
+++ 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconLayoutFeature.java
@@ -30,7 +30,8 @@
 public enum ReconLayoutFeature {
   // Represents the starting point for Recon's layout versioning system.
   INITIAL_VERSION(0, "Recon Layout Versioning Introduction"),
-  TASK_STATUS_STATISTICS(1, "Recon Task Status Statistics Tracking 
Introduced");
+  TASK_STATUS_STATISTICS(1, "Recon Task Status Statistics Tracking 
Introduced"),
+  UNHEALTHY_CONTAINER_REPLICA_MISMATCH(2, "Adding replica mismatch state to 
the unhealthy container table");
 
   private final int version;
   private final String description;
diff --git 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/UnhealthyContainerReplicaMismatchAction.java
 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/UnhealthyContainerReplicaMismatchAction.java
new file mode 100644
index 0000000000..3ff8a51ccb
--- /dev/null
+++ 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/UnhealthyContainerReplicaMismatchAction.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.recon.upgrade;
+
+import static 
org.apache.hadoop.ozone.recon.upgrade.ReconLayoutFeature.UNHEALTHY_CONTAINER_REPLICA_MISMATCH;
+import static 
org.apache.hadoop.ozone.recon.upgrade.ReconUpgradeAction.UpgradeActionType.FINALIZE;
+import static org.hadoop.ozone.recon.codegen.SqlDbUtils.TABLE_EXISTS_CHECK;
+import static 
org.hadoop.ozone.recon.schema.ContainerSchemaDefinition.UNHEALTHY_CONTAINERS_TABLE_NAME;
+import static org.jooq.impl.DSL.field;
+import static org.jooq.impl.DSL.name;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Arrays;
+import javax.sql.DataSource;
+import org.apache.hadoop.ozone.recon.scm.ReconStorageContainerManagerFacade;
+import org.hadoop.ozone.recon.schema.ContainerSchemaDefinition;
+import org.jooq.DSLContext;
+import org.jooq.impl.DSL;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Upgrade action for handling the addition of a new unhealthy container state 
in Recon, which will be for containers,
+ * that have replicas with different data checksums.
+ */
+@UpgradeActionRecon(feature = UNHEALTHY_CONTAINER_REPLICA_MISMATCH, type = 
FINALIZE)
+public class UnhealthyContainerReplicaMismatchAction implements 
ReconUpgradeAction {
+  private static final Logger LOG = 
LoggerFactory.getLogger(InitialConstraintUpgradeAction.class);
+  private DataSource dataSource;
+  private DSLContext dslContext;
+
+  @Override
+  public void execute(ReconStorageContainerManagerFacade scmFacade) throws 
Exception {
+    this.dataSource = scmFacade.getDataSource();
+    try (Connection conn = dataSource.getConnection()) {
+      if (!TABLE_EXISTS_CHECK.test(conn, UNHEALTHY_CONTAINERS_TABLE_NAME)) {
+        return;
+      }
+      dslContext = DSL.using(conn);
+      // Drop the existing constraint
+      dropConstraint();
+      // Add the updated constraint with all enum states
+      addUpdatedConstraint();
+    } catch (SQLException e) {
+      throw new SQLException("Failed to execute 
UnhealthyContainerReplicaMismatchAction", e);
+    }
+  }
+
+  /**
+   * Drops the existing constraint from the UNHEALTHY_CONTAINERS table.
+   */
+  private void dropConstraint() {
+    String constraintName = UNHEALTHY_CONTAINERS_TABLE_NAME + "ck1";
+    dslContext.alterTable(UNHEALTHY_CONTAINERS_TABLE_NAME)
+        .dropConstraint(constraintName)
+        .execute();
+    LOG.debug("Dropped the existing constraint: {}", constraintName);
+  }
+
+  /**
+   * Adds the updated constraint directly within this class.
+   */
+  private void addUpdatedConstraint() {
+    String[] enumStates = Arrays
+        .stream(ContainerSchemaDefinition.UnHealthyContainerStates.values())
+        .map(Enum::name)
+        .toArray(String[]::new);
+
+    
dslContext.alterTable(ContainerSchemaDefinition.UNHEALTHY_CONTAINERS_TABLE_NAME)
+        
.add(DSL.constraint(ContainerSchemaDefinition.UNHEALTHY_CONTAINERS_TABLE_NAME + 
"ck1")
+            .check(field(name("container_state"))
+                .in(enumStates)))
+        .execute();
+
+    LOG.info("Added the updated constraint to the UNHEALTHY_CONTAINERS table 
for enum state values: {}",
+        Arrays.toString(enumStates));
+  }
+
+  @Override
+  public UpgradeActionType getType() {
+    return FINALIZE;
+  }
+}
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 9efd3c9e99..2085503079 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
@@ -731,7 +731,7 @@ public void testGetMissingContainers() throws IOException, 
TimeoutException {
     uuid2 = newDatanode("host2", "127.0.0.2");
     uuid3 = newDatanode("host3", "127.0.0.3");
     uuid4 = newDatanode("host4", "127.0.0.4");
-    createUnhealthyRecords(5, 0, 0, 0);
+    createUnhealthyRecords(5, 0, 0, 0, 0);
 
     Response responseWithLimit = containerEndpoint.getMissingContainers(3);
     MissingContainersResponse responseWithLimitObject
@@ -744,6 +744,9 @@ public void testGetMissingContainers() throws IOException, 
TimeoutException {
     assertTrue(containerWithLimit.getReplicas().stream()
         .map(ContainerHistory::getState)
         .allMatch(s -> s.equals("UNHEALTHY")));
+    assertTrue(containerWithLimit.getReplicas().stream()
+            .map(ContainerHistory::getDataChecksum)
+            .allMatch(s -> s.equals(1234L)));
 
     Collection<MissingContainerMetadata> recordsWithLimit
         = responseWithLimitObject.getContainers();
@@ -808,12 +811,12 @@ public void testUnhealthyContainers() throws IOException, 
TimeoutException {
 
     assertEquals(Collections.EMPTY_LIST, responseObject.getContainers());
 
-    putContainerInfos(14);
+    putContainerInfos(15);
     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);
+    createUnhealthyRecords(5, 4, 3, 2, 1);
 
     response = containerEndpoint.getUnhealthyContainers(1000, 1);
 
@@ -822,6 +825,7 @@ public void testUnhealthyContainers() throws IOException, 
TimeoutException {
     assertEquals(4, responseObject.getOverReplicatedCount());
     assertEquals(3, responseObject.getUnderReplicatedCount());
     assertEquals(2, responseObject.getMisReplicatedCount());
+    assertEquals(1, responseObject.getReplicaMismatchCount());
 
     Collection<UnhealthyContainerMetadata> records
         = responseObject.getContainers();
@@ -890,6 +894,21 @@ public void testUnhealthyContainers() throws IOException, 
TimeoutException {
     assertEquals(12345L, misRep.get(0).getUnhealthySince());
     assertEquals(13L, misRep.get(0).getContainerID());
     assertEquals("some reason", misRep.get(0).getReason());
+
+    List<UnhealthyContainerMetadata> replicaMismatch = records
+        .stream()
+        .filter(r -> r.getContainerState()
+            .equals(UnHealthyContainerStates.REPLICA_MISMATCH.toString()))
+        .collect(Collectors.toList());
+    assertEquals(1, replicaMismatch.size());
+    assertEquals(3, replicaMismatch.get(0).getExpectedReplicaCount());
+    assertEquals(3, replicaMismatch.get(0).getActualReplicaCount());
+    assertEquals(0, replicaMismatch.get(0).getReplicaDeltaCount());
+    assertEquals(12345L, replicaMismatch.get(0).getUnhealthySince());
+    assertEquals(15L, replicaMismatch.get(0).getContainerID());
+    List<ContainerHistory> replicas = replicaMismatch.get(0).getReplicas();
+    assertTrue(replicas.stream().anyMatch(checksum -> 
checksum.getDataChecksum() == 1234L));
+    assertTrue(replicas.stream().anyMatch(checksum -> 
checksum.getDataChecksum() == 2345L));
   }
 
   @Test
@@ -918,7 +937,7 @@ public void testUnhealthyContainersFilteredResponse()
     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);
+    createUnhealthyRecords(5, 4, 3, 2, 1);
     createEmptyMissingUnhealthyRecords(2); // For EMPTY_MISSING state
     createNegativeSizeUnhealthyRecords(2); // For NEGATIVE_SIZE state
 
@@ -932,6 +951,7 @@ public void testUnhealthyContainersFilteredResponse()
     assertEquals(4, responseObject.getOverReplicatedCount());
     assertEquals(3, responseObject.getUnderReplicatedCount());
     assertEquals(2, responseObject.getMisReplicatedCount());
+    assertEquals(1, responseObject.getReplicaMismatchCount());
 
     Collection<UnhealthyContainerMetadata> records = 
responseObject.getContainers();
     assertTrue(records.stream()
@@ -976,7 +996,7 @@ public void testUnhealthyContainersPaging()
     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);
+    createUnhealthyRecords(5, 4, 3, 2, 0);
     UnhealthyContainersResponse firstBatch =
         (UnhealthyContainersResponse) containerEndpoint.getUnhealthyContainers(
             3, 1).getEntity();
@@ -1011,12 +1031,12 @@ public void testGetReplicaHistoryForContainer() throws 
IOException {
     final UUID u2 = newDatanode("host2", "127.0.0.2");
     final UUID u3 = newDatanode("host3", "127.0.0.3");
     final UUID u4 = newDatanode("host4", "127.0.0.4");
-    reconContainerManager.upsertContainerHistory(1L, u1, 1L, 1L, "OPEN");
-    reconContainerManager.upsertContainerHistory(1L, u2, 2L, 1L, "OPEN");
-    reconContainerManager.upsertContainerHistory(1L, u3, 3L, 1L, "OPEN");
-    reconContainerManager.upsertContainerHistory(1L, u4, 4L, 1L, "OPEN");
+    reconContainerManager.upsertContainerHistory(1L, u1, 1L, 1L, "OPEN", 
1234L);
+    reconContainerManager.upsertContainerHistory(1L, u2, 2L, 1L, "OPEN", 
1234L);
+    reconContainerManager.upsertContainerHistory(1L, u3, 3L, 1L, "OPEN", 
1234L);
+    reconContainerManager.upsertContainerHistory(1L, u4, 4L, 1L, "OPEN", 
1234L);
 
-    reconContainerManager.upsertContainerHistory(1L, u1, 5L, 1L, "OPEN");
+    reconContainerManager.upsertContainerHistory(1L, u1, 5L, 1L, "OPEN", 
1234L);
 
     Response response = containerEndpoint.getReplicaHistoryForContainer(1L);
     List<ContainerHistory> histories =
@@ -1024,6 +1044,9 @@ public void testGetReplicaHistoryForContainer() throws 
IOException {
     assertTrue(histories.stream()
         .map(ContainerHistory::getState)
         .allMatch(s -> s.equals("OPEN")));
+    assertTrue(histories.stream()
+            .map(ContainerHistory::getDataChecksum)
+            .allMatch(s -> s.equals(1234L)));
     Set<String> datanodes = Collections.unmodifiableSet(
         new HashSet<>(Arrays.asList(
             u1.toString(), u2.toString(), u3.toString(), u4.toString())));
@@ -1063,7 +1086,7 @@ private void createEmptyMissingUnhealthyRecords(int 
emptyMissing) {
     int cid = 0;
     for (int i = 0; i < emptyMissing; i++) {
       createUnhealthyRecord(++cid, 
UnHealthyContainerStates.EMPTY_MISSING.toString(),
-          3, 3, 0, null);
+          3, 3, 0, null, false);
     }
   }
 
@@ -1071,37 +1094,42 @@ private void createNegativeSizeUnhealthyRecords(int 
negativeSize) {
     int cid = 0;
     for (int i = 0; i < negativeSize; i++) {
       createUnhealthyRecord(++cid, 
UnHealthyContainerStates.NEGATIVE_SIZE.toString(),
-          3, 3, 0, null); // Added for NEGATIVE_SIZE state
+          3, 3, 0, null, false); // Added for NEGATIVE_SIZE state
     }
   }
 
 
   private void createUnhealthyRecords(int missing, int overRep, int underRep,
-                                      int misRep) {
+                                      int misRep, int dataChecksum) {
     int cid = 0;
     for (int i = 0; i < missing; i++) {
       createUnhealthyRecord(++cid, UnHealthyContainerStates.MISSING.toString(),
-          3, 0, 3, null);
+          3, 0, 3, null, false);
     }
     for (int i = 0; i < overRep; i++) {
       createUnhealthyRecord(++cid,
           UnHealthyContainerStates.OVER_REPLICATED.toString(),
-          3, 5, -2, null);
+          3, 5, -2, null, false);
     }
     for (int i = 0; i < underRep; i++) {
       createUnhealthyRecord(++cid,
           UnHealthyContainerStates.UNDER_REPLICATED.toString(),
-          3, 1, 2, null);
+          3, 1, 2, null, false);
     }
     for (int i = 0; i < misRep; i++) {
       createUnhealthyRecord(++cid,
           UnHealthyContainerStates.MIS_REPLICATED.toString(),
-          2, 1, 1, "some reason");
+          2, 1, 1, "some reason", false);
+    }
+    for (int i = 0; i < dataChecksum; i++) {
+      createUnhealthyRecord(++cid,
+          UnHealthyContainerStates.REPLICA_MISMATCH.toString(),
+          3, 3, 0, null, true);
     }
   }
 
   private void createUnhealthyRecord(int id, String state, int expected,
-                                     int actual, int delta, String reason) {
+                                     int actual, int delta, String reason, 
boolean dataChecksumMismatch) {
     long cID = Integer.toUnsignedLong(id);
     UnhealthyContainers missing = new UnhealthyContainers();
     missing.setContainerId(cID);
@@ -1116,14 +1144,16 @@ private void createUnhealthyRecord(int id, String 
state, int expected,
     missingList.add(missing);
     containerHealthSchemaManager.insertUnhealthyContainerRecords(missingList);
 
+    long differentChecksum = dataChecksumMismatch ? 2345L : 1234L;
+
     reconContainerManager.upsertContainerHistory(cID, uuid1, 1L, 1L,
-        "UNHEALTHY");
+        "UNHEALTHY", differentChecksum);
     reconContainerManager.upsertContainerHistory(cID, uuid2, 2L, 1L,
-        "UNHEALTHY");
+        "UNHEALTHY", differentChecksum);
     reconContainerManager.upsertContainerHistory(cID, uuid3, 3L, 1L,
-        "UNHEALTHY");
+        "UNHEALTHY", 1234L);
     reconContainerManager.upsertContainerHistory(cID, uuid4, 4L, 1L,
-        "UNHEALTHY");
+        "UNHEALTHY", 1234L);
   }
 
   protected ContainerWithPipeline getTestContainer(
diff --git 
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/fsck/TestContainerHealthStatus.java
 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/fsck/TestContainerHealthStatus.java
index bfa9a534b2..dc488ea303 100644
--- 
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/fsck/TestContainerHealthStatus.java
+++ 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/fsck/TestContainerHealthStatus.java
@@ -175,6 +175,40 @@ public void testOverReplicatedContainer() {
     assertEquals(0, status.misReplicatedDelta());
   }
 
+  @Test
+  public void testSameDataChecksumContainer() {
+    Set<ContainerReplica> replicas = generateReplicas(container,
+            ContainerReplicaProto.State.CLOSED,
+            ContainerReplicaProto.State.CLOSED,
+            ContainerReplicaProto.State.CLOSED);
+    ContainerHealthStatus status =
+            new ContainerHealthStatus(container, replicas, placementPolicy,
+                    reconContainerMetadataManager, CONF);
+    assertTrue(status.isHealthilyReplicated());
+    assertFalse(status.isMissing());
+    assertFalse(status.isUnderReplicated());
+    assertFalse(status.isOverReplicated());
+    assertFalse(status.isMisReplicated());
+    assertFalse(status.isDataChecksumMismatched());
+  }
+
+  @Test
+  public void testDataChecksumMismatchContainer() {
+    Set<ContainerReplica> replicas = generateMismatchedReplicas(container,
+            ContainerReplicaProto.State.CLOSED,
+            ContainerReplicaProto.State.CLOSED,
+            ContainerReplicaProto.State.CLOSED);
+    ContainerHealthStatus status =
+            new ContainerHealthStatus(container, replicas, placementPolicy,
+                    reconContainerMetadataManager, CONF);
+    assertTrue(status.isHealthilyReplicated());
+    assertFalse(status.isMissing());
+    assertFalse(status.isUnderReplicated());
+    assertFalse(status.isOverReplicated());
+    assertFalse(status.isMisReplicated());
+    assertTrue(status.isDataChecksumMismatched());
+  }
+
   /**
    * Starting with a ContainerHealthStatus of 1 over-replicated container
    * replica and then updating a datanode to one of the out-of-service states.
@@ -378,12 +412,29 @@ public void testMisReplicated() {
   private Set<ContainerReplica> generateReplicas(ContainerInfo cont,
       ContainerReplicaProto.State...states) {
     Set<ContainerReplica> replicas = new HashSet<>();
+    for (ContainerReplicaProto.State s : states) {
+      replicas.add(new ContainerReplica.ContainerReplicaBuilder()
+          .setContainerID(cont.containerID())
+          .setDatanodeDetails(MockDatanodeDetails.randomDatanodeDetails())
+          .setDataChecksum(1234L)
+          .setContainerState(s)
+          .build());
+    }
+    return replicas;
+  }
+
+  private Set<ContainerReplica> generateMismatchedReplicas(ContainerInfo cont,
+                                                           
ContainerReplicaProto.State...states) {
+    Set<ContainerReplica> replicas = new HashSet<>();
+    long checksum = 1234L;
     for (ContainerReplicaProto.State s : states) {
       replicas.add(new ContainerReplica.ContainerReplicaBuilder()
           .setContainerID(cont.containerID())
           .setDatanodeDetails(MockDatanodeDetails.randomDatanodeDetails())
           .setContainerState(s)
+          .setDataChecksum(checksum)
           .build());
+      checksum++;
     }
     return replicas;
   }
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 a8037bcab4..2844ba9855 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
@@ -109,7 +109,7 @@ public void testRun() throws Exception {
     // Create 7 containers. The first 5 will have various unhealthy states
     // defined below. The container with ID=6 will be healthy and
     // container with ID=7 will be EMPTY_MISSING (but not inserted into DB)
-    List<ContainerInfo> mockContainers = getMockContainers(7);
+    List<ContainerInfo> mockContainers = getMockContainers(8);
     when(scmMock.getScmServiceProvider()).thenReturn(scmClientMock);
     when(scmMock.getContainerManager()).thenReturn(containerManagerMock);
     when(containerManagerMock.getContainers(any(ContainerID.class),
@@ -178,6 +178,15 @@ public void testRun() throws Exception {
     when(reconContainerMetadataManager.getKeyCountForContainer(
         7L)).thenReturn(5L);  // Indicates non-empty container 7 for now
 
+    // container ID 8 - REPLICA_MISMATCH
+    ContainerInfo containerInfo8 =
+        
TestContainerInfo.newBuilderForTest().setContainerID(8).setReplicationConfig(replicationConfig).build();
+    
when(containerManagerMock.getContainer(ContainerID.valueOf(8L))).thenReturn(containerInfo8);
+    Set<ContainerReplica> mismatchReplicas = 
getMockReplicasChecksumMismatch(8L,
+        State.CLOSED, State.CLOSED, State.CLOSED);
+    
when(containerManagerMock.getContainerReplicas(containerInfo8.containerID()))
+        .thenReturn(mismatchReplicas);
+
     List<UnhealthyContainers> all = unHealthyContainersTableHandle.findAll();
     assertThat(all).isEmpty();
 
@@ -196,7 +205,7 @@ public void testRun() throws Exception {
 
     // Ensure unhealthy container count in DB matches expected
     LambdaTestUtils.await(60000, 1000, () ->
-        (unHealthyContainersTableHandle.count() == 5));
+        (unHealthyContainersTableHandle.count() == 6));
 
     // Check for UNDER_REPLICATED container states
     UnhealthyContainers rec =
@@ -237,6 +246,12 @@ public void testRun() throws Exception {
     assertEquals(1, rec.getActualReplicaCount().intValue());
     assertNotNull(rec.getReason());
 
+    rec = unHealthyContainersTableHandle.fetchByContainerId(8L).get(0);
+    assertEquals("REPLICA_MISMATCH", rec.getContainerState());
+    assertEquals(0, rec.getReplicaDelta().intValue());
+    assertEquals(3, rec.getExpectedReplicaCount().intValue());
+    assertEquals(3, rec.getActualReplicaCount().intValue());
+
     ReconTaskStatus taskStatus =
         reconTaskStatusDao.findById(containerHealthTask.getTaskName());
     assertThat(taskStatus.getLastUpdatedTimestamp())
@@ -268,7 +283,7 @@ public void testRun() throws Exception {
 
     // Ensure count is reduced after EMPTY_MISSING containers are not inserted
     LambdaTestUtils.await(60000, 1000, () ->
-        (unHealthyContainersTableHandle.count() == 2));
+        (unHealthyContainersTableHandle.count() == 3));
 
     rec = unHealthyContainersTableHandle.fetchByContainerId(1L).get(0);
     assertEquals("UNDER_REPLICATED", rec.getContainerState());
@@ -292,7 +307,7 @@ public void testRun() throws Exception {
 
     // Just check once again that count remains consistent
     LambdaTestUtils.await(60000, 1000, () ->
-        (unHealthyContainersTableHandle.count() == 2));
+        (unHealthyContainersTableHandle.count() == 3));
   }
 
   @Test
@@ -419,6 +434,7 @@ public void testAllContainerStateInsertions() {
 
       case MIS_REPLICATED:
       case NEGATIVE_SIZE:
+      case REPLICA_MISMATCH:
         unhealthyContainer.setExpectedReplicaCount(3);
         unhealthyContainer.setActualReplicaCount(3);
         unhealthyContainer.setReplicaDelta(0);
@@ -580,7 +596,25 @@ private Set<ContainerReplica> getMockReplicas(
           .setContainerState(s)
           .setContainerID(ContainerID.valueOf(containerId))
           .setSequenceId(1)
+          .setDataChecksum(1234L)
+          .build());
+    }
+    return replicas;
+  }
+
+  private Set<ContainerReplica> getMockReplicasChecksumMismatch(
+      long containerId, State...states) {
+    Set<ContainerReplica> replicas = new HashSet<>();
+    long checksum = 1234L;
+    for (State s : states) {
+      replicas.add(ContainerReplica.newBuilder()
+          .setDatanodeDetails(MockDatanodeDetails.randomDatanodeDetails())
+          .setContainerState(s)
+          .setContainerID(ContainerID.valueOf(containerId))
+          .setSequenceId(1)
+          .setDataChecksum(checksum)
           .build());
+      checksum++;
     }
     return replicas;
   }
diff --git 
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/fsck/TestContainerHealthTaskRecordGenerator.java
 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/fsck/TestContainerHealthTaskRecordGenerator.java
index 6210d46ca3..e70ef8007e 100644
--- 
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/fsck/TestContainerHealthTaskRecordGenerator.java
+++ 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/fsck/TestContainerHealthTaskRecordGenerator.java
@@ -361,6 +361,38 @@ public void testCorrectRecordsGenerated() {
     logUnhealthyContainerStats(unhealthyContainerStateStatsMap);
     initializeUnhealthyContainerStateStatsMap(unhealthyContainerStateStatsMap);
 
+    // Replica mismatch
+    replicas = generateMismatchedReplicas(container, CLOSED, CLOSED, CLOSED);
+    status =
+            new ContainerHealthStatus(container, replicas, placementPolicy,
+                    reconContainerMetadataManager, CONF);
+    records = ContainerHealthTask.ContainerHealthRecords
+            .generateUnhealthyRecords(status, (long) 1234567,
+                    unhealthyContainerStateStatsMap);
+    assertEquals(1, records.size());
+    assertEquals(1, unhealthyContainerStateStatsMap.get(
+                    UnHealthyContainerStates.REPLICA_MISMATCH)
+            .getOrDefault(CONTAINER_COUNT, 0L));
+
+    logUnhealthyContainerStats(unhealthyContainerStateStatsMap);
+    initializeUnhealthyContainerStateStatsMap(unhealthyContainerStateStatsMap);
+
+    // Same data checksum replicas
+    replicas = generateReplicas(container, CLOSED, CLOSED, CLOSED);
+    status =
+            new ContainerHealthStatus(container, replicas, placementPolicy,
+                    reconContainerMetadataManager, CONF);
+    records = ContainerHealthTask.ContainerHealthRecords
+            .generateUnhealthyRecords(status, (long) 1234567,
+                    unhealthyContainerStateStatsMap);
+    assertEquals(0, records.size());
+    assertEquals(0, unhealthyContainerStateStatsMap.get(
+                    UnHealthyContainerStates.REPLICA_MISMATCH)
+            .getOrDefault(CONTAINER_COUNT, 0L));
+
+    logUnhealthyContainerStats(unhealthyContainerStateStatsMap);
+    initializeUnhealthyContainerStateStatsMap(unhealthyContainerStateStatsMap);
+
     // Under and Mis Replicated - expect 2 records - mis and under replicated
     replicas =
         generateReplicas(container, CLOSED, CLOSED);
@@ -611,11 +643,28 @@ private Set<ContainerReplica> 
generateReplicas(ContainerInfo cont,
           .setContainerID(cont.containerID())
           .setDatanodeDetails(MockDatanodeDetails.randomDatanodeDetails())
           .setContainerState(s)
+          .setDataChecksum(1234L)
           .build());
     }
     return replicas;
   }
 
+  private Set<ContainerReplica> generateMismatchedReplicas(ContainerInfo cont,
+                                                 
ContainerReplicaProto.State...states) {
+    Set<ContainerReplica> replicas = new HashSet<>();
+    long checksum = 1234L;
+    for (ContainerReplicaProto.State s : states) {
+      replicas.add(new ContainerReplica.ContainerReplicaBuilder()
+          .setContainerID(cont.containerID())
+          .setDatanodeDetails(MockDatanodeDetails.randomDatanodeDetails())
+          .setContainerState(s)
+          .setDataChecksum(checksum)
+          .build());
+      checksum++;
+    }
+    return replicas;
+  }
+
   private void initializeUnhealthyContainerStateStatsMap(
       Map<UnHealthyContainerStates, Map<String, Long>>
           unhealthyContainerStateStatsMap) {
@@ -631,6 +680,8 @@ private void initializeUnhealthyContainerStateStatsMap(
         UnHealthyContainerStates.MIS_REPLICATED, new HashMap<>());
     unhealthyContainerStateStatsMap.put(
         UnHealthyContainerStates.NEGATIVE_SIZE, new HashMap<>());
+    unhealthyContainerStateStatsMap.put(
+        UnHealthyContainerStates.REPLICA_MISMATCH, new HashMap<>());
   }
 
   private void logUnhealthyContainerStats(
diff --git 
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconContainerManager.java
 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconContainerManager.java
index abbc4bce1d..81c9166b13 100644
--- 
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconContainerManager.java
+++ 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconContainerManager.java
@@ -209,7 +209,7 @@ public void testUpdateAndRemoveContainerReplica()
         .setUuid(uuid1).setHostName("host1").setIpAddress("127.0.0.1").build();
     ContainerReplica containerReplica1 = ContainerReplica.newBuilder()
         .setContainerID(containerID1).setContainerState(State.OPEN)
-        .setDatanodeDetails(datanodeDetails1).setSequenceId(1001L).build();
+        
.setDatanodeDetails(datanodeDetails1).setSequenceId(1001L).setDataChecksum(1234L).build();
 
     final ReconContainerManager containerManager = getContainerManager();
     final Map<Long, Map<UUID, ContainerReplicaHistory>> repHistMap =
@@ -237,6 +237,7 @@ public void testUpdateAndRemoveContainerReplica()
     assertEquals(repHist1.getLastSeenTime(), repHist1.getFirstSeenTime());
     assertEquals(containerReplica1.getSequenceId().longValue(),
         repHist1.getBcsId());
+    assertEquals(containerReplica1.getDataChecksum(), 
repHist1.getDataChecksum());
 
     // Let's update the entry again
     containerReplica1 = ContainerReplica.newBuilder()
@@ -255,7 +256,7 @@ public void testUpdateAndRemoveContainerReplica()
         .setUuid(uuid2).setHostName("host2").setIpAddress("127.0.0.2").build();
     final ContainerReplica containerReplica2 = ContainerReplica.newBuilder()
         .setContainerID(containerID1).setContainerState(State.OPEN)
-        .setDatanodeDetails(datanodeDetails2).setSequenceId(1051L).build();
+        
.setDatanodeDetails(datanodeDetails2).setSequenceId(1051L).setDataChecksum(1234L).build();
 
     // Add replica to DN02
     containerManager.updateContainerReplica(containerID1, containerReplica2);
@@ -269,6 +270,7 @@ public void testUpdateAndRemoveContainerReplica()
     // Because this is a new entry, first seen time equals last seen time
     assertEquals(repHist2.getLastSeenTime(), repHist2.getFirstSeenTime());
     assertEquals(1051L, repHist2.getBcsId());
+    assertEquals(containerReplica2.getDataChecksum(), 
repHist2.getDataChecksum());
 
     // Remove replica from DN01
     containerManager.removeContainerReplica(containerID1, containerReplica1);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to