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

errose28 pushed a commit to branch HDDS-14496-zdu
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/HDDS-14496-zdu by this push:
     new 2f149e45a92 HDDS-14687. Add test that OM can finalize from a snapshot 
(#10832)
2f149e45a92 is described below

commit 2f149e45a924f62168b9395b74cc0f84077950f8
Author: Ethan Rose <[email protected]>
AuthorDate: Thu Jul 23 09:04:05 2026 -0400

    HDDS-14687. Add test that OM can finalize from a snapshot (#10832)
---
 .../hadoop/ozone/om/TestOMUpgradeFinalization.java | 185 +++++++++++++++++++++
 1 file changed, 185 insertions(+)

diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOMUpgradeFinalization.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOMUpgradeFinalization.java
new file mode 100644
index 00000000000..c2b7d0d3b73
--- /dev/null
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOMUpgradeFinalization.java
@@ -0,0 +1,185 @@
+/*
+ * 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.om;
+
+import static org.apache.hadoop.ozone.OzoneConsts.APPARENT_VERSION_KEY;
+import static 
org.apache.hadoop.ozone.om.TestOzoneManagerHAWithStoppedNodes.createKey;
+import static 
org.apache.hadoop.ozone.om.upgrade.OMLayoutFeature.INITIAL_VERSION;
+import static org.apache.ozone.test.GenericTestUtils.waitFor;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.io.IOException;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.conf.StorageUnit;
+import org.apache.hadoop.ozone.MiniOzoneCluster;
+import org.apache.hadoop.ozone.MiniOzoneHAClusterImpl;
+import org.apache.hadoop.ozone.OzoneManagerVersion;
+import org.apache.hadoop.ozone.TestDataUtil;
+import org.apache.hadoop.ozone.client.ObjectStore;
+import org.apache.hadoop.ozone.client.OzoneBucket;
+import org.apache.hadoop.ozone.client.OzoneClient;
+import org.apache.hadoop.ozone.conf.OMClientConfig;
+import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol;
+import org.apache.hadoop.ozone.om.ratis.OzoneManagerRatisServer;
+import org.apache.hadoop.ozone.upgrade.RatisBasedVersionManager;
+import org.apache.ozone.test.GenericTestUtils.LogCapturer;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Tests for OM upgrade finalization.
+ */
+class TestOMUpgradeFinalization {
+
+  private static final Logger LOG = 
LoggerFactory.getLogger(TestOMUpgradeFinalization.class);
+
+  // Force Ratis to take a snapshot and purge logs after only a few
+  // transactions, so a lagging OM has to install a snapshot on startup.
+  private static final long SNAPSHOT_THRESHOLD = 5;
+  private static final int LOG_PURGE_GAP = 5;
+
+  /**
+   * A lagging OM that has to install a Ratis snapshot should finalize
+   * from that snapshot.
+   * When finalization runs on the active OMs while one OM is down, the down OM
+   * cannot replay the finalization from the (purged) logs, so it must pick up
+   * the finalized version from the checkpoint it downloads from the leader.
+   */
+  @Test
+  void testFinalizationFromSnapshot() throws Exception {
+    OzoneConfiguration conf = new OzoneConfiguration();
+    conf.set(OMConfigKeys.OZONE_OM_UPGRADE_FINALIZATION_CHECK_INTERVAL, 
"10ms");
+    // Force frequent snapshots and log purge so the inactive OM must install a
+    // snapshot from the leader (rather than replay logs) when it starts.
+    conf.setInt(OMConfigKeys.OZONE_OM_RATIS_LOG_PURGE_GAP, LOG_PURGE_GAP);
+    conf.setStorageSize(OMConfigKeys.OZONE_OM_RATIS_SEGMENT_SIZE_KEY, 16, 
StorageUnit.KB);
+    
conf.setStorageSize(OMConfigKeys.OZONE_OM_RATIS_SEGMENT_PREALLOCATED_SIZE_KEY, 
16, StorageUnit.KB);
+    
conf.setLong(OMConfigKeys.OZONE_OM_RATIS_SNAPSHOT_AUTO_TRIGGER_THRESHOLD_KEY, 
SNAPSHOT_THRESHOLD);
+
+    // Use a short client RPC timeout so the first request fails over quickly 
to
+    // a responsive OM instead of blocking on the 15-minute default if it 
lands on the inactive one.
+    OMClientConfig clientConfig = conf.getObject(OMClientConfig.class);
+    clientConfig.setRpcTimeOut(TimeUnit.SECONDS.toMillis(5));
+    conf.setFromObject(clientConfig);
+
+    LogCapturer versionManagerLogCapture = 
LogCapturer.captureLogs(RatisBasedVersionManager.class);
+    LogCapturer omLogCapture = LogCapturer.captureLogs(OzoneManager.class);
+
+    try (MiniOzoneHAClusterImpl cluster = newCluster(conf)) {
+      LOG.info("Waiting for cluster to be ready");
+      cluster.waitForClusterToBeReady();
+      OzoneManager followerOM = cluster.getInactiveOM().next();
+      LOG.info("Cluster ready with inactive OM: {}", followerOM);
+
+      try (OzoneClient client = cluster.newClient()) {
+        LOG.info("Client created");
+        ObjectStore objectStore = client.getObjectStore();
+        LOG.info("Object store created");
+
+        // The active OMs start un-finalized: the apparent version is the 
initial
+        // version and no version has been written to the DB yet.
+        for (OzoneManager om : cluster.getOzoneManagersList()) {
+          if (cluster.isOMActive(om.getOMNodeId())) {
+            assertEquals(INITIAL_VERSION, 
om.getVersionManager().getApparentVersion());
+            
assertNull(om.getMetadataManager().getMetaTable().get(APPARENT_VERSION_KEY));
+          }
+        }
+
+        // Finalize the running (active) OMs.
+        LOG.info("Finalizing OMs");
+        OzoneManagerProtocol omClient = 
objectStore.getClientProxy().getOzoneManagerClient();
+        omClient.finalizeUpgrade();
+        OMUpgradeTestUtils.waitForFinalization(omClient);
+        LOG.info("Finalized active OMs");
+
+        // Write enough keys to advance the log index well past the purge gap 
so
+        // the leader's logs covering finalization are purged. This forces the
+        // follower to install a snapshot instead of replaying the logs.
+        OzoneManager leaderOM = cluster.getOMLeader();
+        assertNotNull(leaderOM);
+        OzoneManagerRatisServer leaderRatisServer = 
leaderOM.getOmRatisServer();
+        OzoneBucket bucket = 
TestDataUtil.createVolumeAndBucket(cluster.newClient());
+        long targetLogIndex = 
leaderRatisServer.getLastAppliedTermIndex().getIndex() + 100;
+        LOG.info("Writing keys to advance log index");
+        writeKeysToIncreaseLogIndex(leaderRatisServer, targetLogIndex, bucket);
+
+        // Start the inactive OM. It downloads the leader's checkpoint (which
+        // already contains the finalized apparent version) and finalizes from 
it.
+        LOG.info("Starting inactive OM");
+        cluster.startInactiveOM(followerOM.getOMNodeId());
+
+        // Wait for the follower to finish installing the snapshot and catch up
+        // to the leader before reading its DB and shutting down, so shutdown
+        // does not race with the tail of the snapshot install.
+        LOG.info("Inactive OM started, waiting to catch up with leader");
+        leaderOM = cluster.getOMLeader();
+        assertNotNull(leaderOM);
+        long leaderIndex = 
leaderOM.getOmRatisServer().getLastAppliedTermIndex().getIndex();
+        waitFor(() -> {
+          boolean running = followerOM.isRunning();
+          // Make sure the snapshot install finishes completely so it does not 
block cluster shutdown on test cleanup.
+          boolean snapshotFinished = 
omLogCapture.getOutput().contains("Install Checkpoint is finished");
+          long followerIndex = 
followerOM.getOmRatisServer().getLastAppliedTermIndex().getIndex();
+          LOG.info("Waiting for follower to catch up with leader. Running? {} 
Snapshot finished? {}" +
+                  " Follower index = {} leader index = {}",
+              running, snapshotFinished, followerIndex, leaderIndex);
+          return running && snapshotFinished && followerIndex >= leaderIndex;
+        }, 1000, 60000);
+        LOG.info("Inactive OM has caught up with leader, checking finalization 
state");
+        omLogCapture.stopCapturing();
+
+        // The follower finalizes to the software version, picked up from the 
installed snapshot.
+        assertEquals(OzoneManagerVersion.SOFTWARE_VERSION, 
followerOM.getVersionManager().getApparentVersion());
+
+        String dbVersion = 
followerOM.getMetadataManager().getMetaTable().get(APPARENT_VERSION_KEY);
+        assertNotNull(dbVersion);
+        assertEquals(OzoneManagerVersion.SOFTWARE_VERSION.serialize(), 
Integer.parseInt(dbVersion));
+
+        // Confirm finalization happened via snapshot install, not log replay.
+        assertThat(versionManagerLogCapture.getOutput()).contains("New 
snapshot received with higher apparent version");
+        versionManagerLogCapture.stopCapturing();
+      }
+    }
+  }
+
+  private static MiniOzoneHAClusterImpl newCluster(OzoneConfiguration conf)
+      throws IOException {
+    conf.setInt(OMStorage.TESTING_INIT_APPARENT_VERSION_KEY, 
INITIAL_VERSION.serialize());
+    MiniOzoneHAClusterImpl.Builder builder = 
MiniOzoneCluster.newHABuilder(conf);
+    builder.setOMServiceId(UUID.randomUUID().toString())
+        .setNumOfOzoneManagers(3)
+        .setNumOfActiveOMs(2)
+        .setNumDatanodes(1);
+    return builder.build();
+  }
+
+  private static void writeKeysToIncreaseLogIndex(OzoneManagerRatisServer 
omRatisServer,
+      long targetLogIndex, OzoneBucket bucket) throws IOException {
+    long logIndex = omRatisServer.getLastAppliedTermIndex().getIndex();
+    while (logIndex < targetLogIndex) {
+      createKey(bucket);
+      logIndex = omRatisServer.getLastAppliedTermIndex().getIndex();
+    }
+  }
+}


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

Reply via email to