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

sadanand48 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 fb9f30b8869 HDDS-16057. OM bootstrap fails when checkpoint install is 
rejected during BOOTSTRAPPING. (#10917)
fb9f30b8869 is described below

commit fb9f30b88694f8fcc876af786d38b86a2aa89d76
Author: Sadanand Shenoy <[email protected]>
AuthorDate: Mon Aug 3 16:05:18 2026 +0530

    HDDS-16057. OM bootstrap fails when checkpoint install is rejected during 
BOOTSTRAPPING. (#10917)
    
    Co-authored-by: Wei-Chiu Chuang <[email protected]>
---
 .../hadoop/ozone/om/TestOMRatisSnapshots.java      | 138 +++++++++++++++++++++
 .../hadoop/ozone/MiniOzoneHAClusterImpl.java       |  16 ++-
 .../org/apache/hadoop/ozone/om/OzoneManager.java   |  10 +-
 3 files changed, 160 insertions(+), 4 deletions(-)

diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOMRatisSnapshots.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOMRatisSnapshots.java
index 1925b6a204a..7de95f58bc2 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOMRatisSnapshots.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOMRatisSnapshots.java
@@ -28,6 +28,7 @@
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
 
 import java.io.File;
 import java.io.IOException;
@@ -51,12 +52,14 @@
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.conf.StorageUnit;
 import org.apache.hadoop.hdds.utils.FaultInjector;
+import org.apache.hadoop.hdds.utils.RDBSnapshotProvider;
 import org.apache.hadoop.hdds.utils.TransactionInfo;
 import org.apache.hadoop.hdds.utils.db.DBCheckpoint;
 import org.apache.hadoop.hdds.utils.db.RDBCheckpointUtils;
 import org.apache.hadoop.hdds.utils.db.RDBStore;
 import org.apache.hadoop.ozone.MiniOzoneCluster;
 import org.apache.hadoop.ozone.MiniOzoneHAClusterImpl;
+import org.apache.hadoop.ozone.OzoneConfigKeys;
 import org.apache.hadoop.ozone.client.BucketArgs;
 import org.apache.hadoop.ozone.client.ObjectStore;
 import org.apache.hadoop.ozone.client.OzoneBucket;
@@ -72,6 +75,7 @@
 import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
 import org.apache.hadoop.ozone.om.ratis.OzoneManagerRatisServer;
 import org.apache.hadoop.ozone.om.ratis.OzoneManagerRatisServerConfig;
+import org.apache.hadoop.ozone.om.ratis.OzoneManagerStateMachine;
 import org.apache.hadoop.ozone.om.ratis.utils.OzoneManagerRatisUtils;
 import org.apache.ozone.test.GenericTestUtils;
 import org.apache.ozone.test.GenericTestUtils.LogCapturer;
@@ -93,8 +97,14 @@
  */
 public class TestOMRatisSnapshots {
   private static final String OM_SERVICE_ID = "om-service-test1";
+  private static final String BOOTSTRAP_OM_SERVICE_ID = "om-service-bootstrap";
   private static final int NUM_OF_OMS = 3;
 
+  private static final int BOOTSTRAP_LOG_PURGE_GAP = 5;
+  private static final long BOOTSTRAP_TARGET_LOG_INDEX = 200;
+  private static final int BOOTSTRAP_INSTALL_START_DEADLINE_MS = 30_000;
+  private static final int BOOTSTRAP_COMPLETION_DEADLINE_MS = 60_000;
+
   private MiniOzoneHAClusterImpl cluster = null;
   private ObjectStore objectStore;
   private OzoneConfiguration conf;
@@ -597,6 +607,134 @@ public void 
testInstallSnapshotFromLeaderFailedDownloadCleanupSucceeds()
     followerOM.getOmSnapshotProvider().setInjector(null);
   }
 
+  /**
+   * Regression test for bootstrap when leader logs are purged: checkpoint 
install
+   * must proceed during {@code BOOTSTRAPPING} with the default v2 checkpoint 
API
+   * and complete successfully.
+   */
+  @Test
+  public void testBootstrapInstallSnapshotDuringBootstrapping() throws 
Exception {
+    IOUtils.closeQuietly(client);
+    if (cluster != null) {
+      cluster.shutdown();
+    }
+
+    OzoneConfiguration bootstrapConf = new OzoneConfiguration();
+    
bootstrapConf.setInt(OzoneConfigKeys.OZONE_CLIENT_FAILOVER_MAX_ATTEMPTS_KEY, 5);
+    bootstrapConf.setInt(OMConfigKeys.OZONE_OM_RATIS_LOG_PURGE_GAP, 
BOOTSTRAP_LOG_PURGE_GAP);
+    
bootstrapConf.setLong(OMConfigKeys.OZONE_OM_RATIS_SNAPSHOT_AUTO_TRIGGER_THRESHOLD_KEY,
+        SNAPSHOT_THRESHOLD);
+    bootstrapConf.setStorageSize(OMConfigKeys.OZONE_OM_RATIS_SEGMENT_SIZE_KEY, 
16,
+        StorageUnit.KB);
+    
bootstrapConf.setStorageSize(OMConfigKeys.OZONE_OM_RATIS_SEGMENT_PREALLOCATED_SIZE_KEY,
+        16, StorageUnit.KB);
+
+    OzoneManagerRatisServerConfig omRatisConf =
+        bootstrapConf.getObject(OzoneManagerRatisServerConfig.class);
+    omRatisConf.setLogAppenderWaitTimeMin(10);
+    bootstrapConf.setFromObject(omRatisConf);
+
+    cluster = (MiniOzoneHAClusterImpl) 
MiniOzoneCluster.newHABuilder(bootstrapConf)
+        .setOMServiceId(BOOTSTRAP_OM_SERVICE_ID)
+        .setNumOfOzoneManagers(2)
+        .setNumDatanodes(1)
+        .build();
+    cluster.waitForClusterToBeReady();
+
+    client = OzoneClientFactory.getRpcClient(BOOTSTRAP_OM_SERVICE_ID, 
bootstrapConf);
+    objectStore = client.getObjectStore();
+    String bootstrapVolume = uniqueObjectName("volume");
+    String bootstrapBucket = uniqueObjectName("bucket");
+    objectStore.createVolume(bootstrapVolume);
+    OzoneVolume volume = objectStore.getVolume(bootstrapVolume);
+    volume.createBucket(bootstrapBucket,
+        BucketArgs.newBuilder().setBucketLayout(TEST_BUCKET_LAYOUT).build());
+    ozoneBucket = volume.getBucket(bootstrapBucket);
+
+    OzoneManager leader = cluster.getOMLeader();
+    writeKeysToIncreaseLogIndex(leader.getOmRatisServer(), 
BOOTSTRAP_TARGET_LOG_INDEX);
+    assertThat(leader.getRatisSnapshotIndex())
+        .as("leader should have purged early logs")
+        .isGreaterThan((long) BOOTSTRAP_LOG_PURGE_GAP);
+
+    LogCapturer omLog = LogCapturer.captureLogs(OzoneManager.class);
+    LogCapturer stateMachineLog =
+        LogCapturer.captureLogs(OzoneManagerStateMachine.class);
+    LogCapturer snapshotProviderLog =
+        LogCapturer.captureLogs(RDBSnapshotProvider.class);
+    String newNodeId = "omNode-bootstrap-ratis-snapshots";
+    ExecutorService executor = Executors.newSingleThreadExecutor();
+    Future<?> bootstrapFuture = executor.submit(() -> {
+      try {
+        cluster.bootstrapOzoneManager(newNodeId);
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+    });
+
+    try {
+      waitForBootstrapCheckpointInstallToStart(omLog, snapshotProviderLog);
+      bootstrapFuture.get(BOOTSTRAP_COMPLETION_DEADLINE_MS, 
TimeUnit.MILLISECONDS);
+      assertBootstrapOmJoinedRatisGroup(newNodeId);
+    } finally {
+      bootstrapFuture.cancel(true);
+      omLog.stopCapturing();
+      stateMachineLog.stopCapturing();
+      snapshotProviderLog.stopCapturing();
+      executor.shutdownNow();
+      if (cluster != null) {
+        cluster.shutdown();
+      }
+    }
+
+    assertThat(stateMachineLog.getOutput())
+        .as("Ratis should notify the bootstrapping OM to install a checkpoint")
+        .contains("Received install snapshot notification from OM leader");
+    assertThat(omLog.getOutput())
+        .as("checkpoint install must not be aborted during BOOTSTRAPPING")
+        .doesNotContain("Abort install snapshot from Leader");
+    assertThat(omLog.getOutput())
+        .as("checkpoint installation should finish")
+        .contains("Install Checkpoint is finished");
+    assertThat(snapshotProviderLog.getOutput())
+        .as("checkpoint download should start after install is accepted")
+        .contains("Prepare to download the snapshot from leader OM");
+    assertThat(snapshotProviderLog.getOutput())
+        .as("checkpoint tarball should be assembled on the bootstrapping OM")
+        .contains("DB snapshot transfer is complete.");
+  }
+
+  private void assertBootstrapOmJoinedRatisGroup(String newNodeId) {
+    OzoneManager newOm = cluster.getOzoneManager(newNodeId);
+    assertNotNull(newOm, "Bootstrapped OM should be registered on the 
cluster");
+    for (OzoneManager om : cluster.getOzoneManagersList()) {
+      assertTrue(om.doesPeerExist(newNodeId),
+          "New OM node " + newNodeId + " not present in peer list of OM " + 
om.getOMNodeId());
+      assertTrue(om.getOmRatisServer().doesPeerExist(newNodeId),
+          "New OM node " + newNodeId + " not present in Ratis peer list of OM "
+              + om.getOMNodeId());
+    }
+  }
+
+  private void waitForBootstrapCheckpointInstallToStart(
+      LogCapturer omLog,
+      LogCapturer snapshotProviderLog)
+      throws InterruptedException, TimeoutException {
+    try {
+      GenericTestUtils.waitFor(() -> {
+        if (omLog.getOutput().contains("Abort install snapshot from Leader")) {
+          fail("Checkpoint install was aborted during BOOTSTRAPPING.");
+        }
+        return snapshotProviderLog.getOutput()
+            .contains("Prepare to download the snapshot from leader OM");
+      }, 200, BOOTSTRAP_INSTALL_START_DEADLINE_MS);
+    } catch (TimeoutException e) {
+      fail("Checkpoint download did not start within " + 
BOOTSTRAP_INSTALL_START_DEADLINE_MS
+          + "ms. OzoneManager log: " + omLog.getOutput()
+          + ", RDBSnapshotProvider log: " + snapshotProviderLog.getOutput());
+    }
+  }
+
   /**
    * Moves all contents from the checkpoint location into the omDbDir.
    * This reorganizes the checkpoint structure so that all checkpoint files
diff --git 
a/hadoop-ozone/mini-cluster/src/main/java/org/apache/hadoop/ozone/MiniOzoneHAClusterImpl.java
 
b/hadoop-ozone/mini-cluster/src/main/java/org/apache/hadoop/ozone/MiniOzoneHAClusterImpl.java
index 507777d8bfe..8df0f587c60 100644
--- 
a/hadoop-ozone/mini-cluster/src/main/java/org/apache/hadoop/ozone/MiniOzoneHAClusterImpl.java
+++ 
b/hadoop-ozone/mini-cluster/src/main/java/org/apache/hadoop/ozone/MiniOzoneHAClusterImpl.java
@@ -881,10 +881,24 @@ private OzoneConfiguration addNewOMToConfig(String 
omServiceId,
 
   /**
    * Update the configurations of the given list of OMs.
+   * Merges {@code newConf} with each OM's existing node-local storage paths so
+   * bootstrap peer updates do not clobber per-node {@code 
ozone.metadata.dirs}.
    */
   private void updateOMConfigs(OzoneConfiguration newConf) {
     for (OzoneManager om : omhaService.getActiveServices()) {
-      om.setConfiguration(newConf);
+      OzoneConfiguration merged = new OzoneConfiguration(newConf);
+      OzoneConfiguration current = om.getConfiguration();
+      copyConfigIfSet(current, merged, OZONE_METADATA_DIRS);
+      copyConfigIfSet(current, merged, OMConfigKeys.OZONE_OM_DB_DIRS);
+      om.setConfiguration(merged);
+    }
+  }
+
+  private static void copyConfigIfSet(OzoneConfiguration from,
+      OzoneConfiguration to, String key) {
+    String value = from.get(key);
+    if (StringUtils.isNotEmpty(value)) {
+      to.set(key, value);
     }
   }
 
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
index 2db20c59f31..e15231dcfc2 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
@@ -4201,9 +4201,9 @@ public List<OzoneAcl> getAcl(OzoneObj obj) throws 
IOException {
    * @throws IOException if download or cleanup fails
    */
   public synchronized TermIndex installSnapshotFromLeader(String leaderId) 
throws IOException {
-    if (!isRunning() || testInstallSnapshot) {
-      LOG.warn("OzoneManager is not in running state, state {}. Abort install 
snapshot from Leader.",
-          omState);
+    if (!isRunningOrBootstrapping() || testInstallSnapshot) {
+      LOG.warn("OzoneManager is not in running state nor bootstrapping, state 
{}. "
+          + "Abort install snapshot from Leader.", omState);
       return null;
     }
 
@@ -4249,6 +4249,10 @@ public synchronized TermIndex 
installSnapshotFromLeader(String leaderId) throws
     return termIndex;
   }
 
+  private boolean isRunningOrBootstrapping() {
+    return omState == State.RUNNING || omState == State.BOOTSTRAPPING;
+  }
+
   private void cleanupCheckpoint(DBCheckpoint omDBCheckpoint) throws 
IOException {
     if (omDBCheckpoint != null) {
       try {


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

Reply via email to