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 13019333036 HDDS-14754. Add ZDU upgrade action to clean up leftover 
state (#10889)
13019333036 is described below

commit 130193330362bf626b0b8e959ba7fd384976f55e
Author: Ethan Rose <[email protected]>
AuthorDate: Mon Aug 3 11:57:23 2026 -0400

    HDDS-14754. Add ZDU upgrade action to clean up leftover state (#10889)
---
 .../java/org/apache/hadoop/ozone/OzoneConsts.java  |  6 --
 .../upgrade/AbstractUpgradeActionProvider.java     |  3 +-
 .../upgrade/TestAbstractUpgradeActionProvider.java | 75 ++++++++++++++++++
 .../ClearFinalizingStateScmUpgradeAction.java}     | 21 +++++-
 .../hadoop/hdds/scm/ha/TestSCMStateMachine.java    | 56 +++++++++++++-
 .../TestClearFinalizingStateScmUpgradeAction.java  | 83 ++++++++++++++++++++
 .../scm/server/upgrade/TestScmVersionManager.java  |  4 +-
 .../upgrade/ClearPreparedStateOmUpgradeAction.java | 60 +++++++++++++++
 .../upgrade/TestOMFinalizeUpgradeRequest.java      | 35 +++++++++
 .../TestClearPreparedStateOmUpgradeAction.java     | 88 ++++++++++++++++++++++
 .../ozone/om/upgrade/TestOMVersionManager.java     |  4 +-
 .../om/upgrade/ZduOmUpgradeActionForTest.java      | 33 --------
 12 files changed, 418 insertions(+), 50 deletions(-)

diff --git 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java
index 6e7dad0a763..c9b8d61d7fc 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java
@@ -407,17 +407,11 @@ public final class OzoneConsts {
   public static final String TRANSACTION_INFO_KEY = "#TRANSACTIONINFO";
   public static final String TRANSACTION_INFO_SPLIT_KEY = "#";
 
-  public static final String PREPARE_MARKER_KEY = "#PREPAREDINFO";
-
   public static final String CONTAINER_DB_TYPE_ROCKSDB = "RocksDB";
 
   // An on-disk transient marker file used when replacing DB with checkpoint
   public static final String DB_TRANSIENT_MARKER = "dbInconsistentMarker";
 
-  // An on-disk marker file used to indicate that the OM is in prepare and
-  // should remain prepared even after a restart.
-  public static final String PREPARE_MARKER = "prepareMarker";
-
   public static final String OZONE_RATIS_SNAPSHOT_DIR = "snapshot";
 
   public static final long DEFAULT_OM_UPDATE_ID = -1L;
diff --git 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/upgrade/AbstractUpgradeActionProvider.java
 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/upgrade/AbstractUpgradeActionProvider.java
index f80514ec5e9..a95dcfda2e1 100644
--- 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/upgrade/AbstractUpgradeActionProvider.java
+++ 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/upgrade/AbstractUpgradeActionProvider.java
@@ -80,8 +80,7 @@ public Map<ComponentVersion, T> load() {
           LOG.info("Registering Upgrade Action : {}", action.name());
           upgradeActions.put(version, action);
         } catch (Exception e) {
-          LOG.error("Cannot instantiate Upgrade Action class {}",
-              clazz.getSimpleName(), e);
+          throw new IllegalStateException("Cannot instantiate Upgrade Action 
class " + clazz.getName(), e);
         }
       } else {
         LOG.warn("Found upgrade action class not of type {} : {}",
diff --git 
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/ozone/upgrade/TestAbstractUpgradeActionProvider.java
 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/ozone/upgrade/TestAbstractUpgradeActionProvider.java
new file mode 100644
index 00000000000..f69ab94d69d
--- /dev/null
+++ 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/ozone/upgrade/TestAbstractUpgradeActionProvider.java
@@ -0,0 +1,75 @@
+/*
+ * 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.upgrade;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.google.common.collect.ImmutableSet;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.apache.hadoop.hdds.ComponentVersion;
+import org.apache.hadoop.hdds.upgrade.HDDSLayoutFeature;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@link AbstractUpgradeActionProvider#load()}.
+ */
+class TestAbstractUpgradeActionProvider {
+
+  /**
+   * An upgrade action whose no-arg constructor fails to instantiate must 
abort {@code load()} rather than
+   * silently drop the action from the returned map, which would finalize its 
version as a no-op.
+   */
+  @Test
+  public void testLoadFailsWhenActionCannotBeInstantiated() {
+    assertThrows(IllegalStateException.class, () -> new 
ThrowingActionProvider().load());
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.TYPE)
+  private @interface TestUpgradeAction {
+  }
+
+  private interface TestAction extends UpgradeAction<Object> {
+  }
+
+  @TestUpgradeAction
+  public static class ThrowingTestAction implements TestAction {
+    ThrowingTestAction() {
+      throw new IllegalArgumentException("cannot construct test upgrade 
action");
+    }
+
+    @Override
+    public void execute(Object arg) {
+    }
+  }
+
+  private static final class ThrowingActionProvider extends 
AbstractUpgradeActionProvider<TestAction> {
+    ThrowingActionProvider() {
+      super(ImmutableSet.of(TestUpgradeAction.class), TestAction.class,
+          TestAbstractUpgradeActionProvider.class.getPackage().getName());
+    }
+
+    @Override
+    protected ComponentVersion extractVersion(Class<?> clazz) {
+      return HDDSLayoutFeature.INITIAL_VERSION;
+    }
+  }
+}
diff --git 
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/upgrade/ZduScmUpgradeActionForTest.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/upgrade/ClearFinalizingStateScmUpgradeAction.java
similarity index 50%
rename from 
hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/upgrade/ZduScmUpgradeActionForTest.java
rename to 
hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/upgrade/ClearFinalizingStateScmUpgradeAction.java
index aa12c999292..70b6641ec1b 100644
--- 
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/upgrade/ZduScmUpgradeActionForTest.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/upgrade/ClearFinalizingStateScmUpgradeAction.java
@@ -20,16 +20,29 @@
 import static org.apache.hadoop.hdds.HDDSVersion.ZDU;
 
 import org.apache.hadoop.hdds.scm.server.OzoneStorageContainerManager;
+import org.apache.hadoop.hdds.scm.server.StorageContainerManager;
 import org.apache.hadoop.hdds.upgrade.ScmUpgradeAction;
+import org.apache.hadoop.hdds.utils.db.Table;
 import org.apache.hadoop.ozone.upgrade.ScmUpgradeActionForVersion;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
- * No-op upgrade action used only to verify that {@link 
ScmUpgradeActionForVersion} is scanned by
- * {@link org.apache.hadoop.hdds.upgrade.ScmUpgradeActionProvider} in tests.
+ * Removes the "finalizing in progress" mark, which may be left over in SCM 
meta table from pre-ZDU
+ * code. Deleting an absent key is a no-op, making the action idempotent.
  */
 @ScmUpgradeActionForVersion(version = ZDU)
-public class ZduScmUpgradeActionForTest implements ScmUpgradeAction {
+public class ClearFinalizingStateScmUpgradeAction implements ScmUpgradeAction {
+  private static final Logger LOG = 
LoggerFactory.getLogger(ClearFinalizingStateScmUpgradeAction.class);
+
+  // Orphan "finalizing in progress" mark written by pre-ZDU SCM code; removed 
on ZDU finalization.
+  private static final String LEGACY_FINALIZING_KEY = "#FINALIZING";
+
   @Override
-  public void execute(OzoneStorageContainerManager arg) {
+  public void execute(OzoneStorageContainerManager context) throws Exception {
+    StorageContainerManager scm = (StorageContainerManager) context;
+    Table<String, String> metaTable = scm.getScmMetadataStore().getMetaTable();
+    scm.getScmHAManager().getDBTransactionBuffer().removeFromBuffer(metaTable, 
LEGACY_FINALIZING_KEY);
+    LOG.info("Removed leftover SCM finalizing mark {} during ZDU 
finalization.", LEGACY_FINALIZING_KEY);
   }
 }
diff --git 
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMStateMachine.java
 
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMStateMachine.java
index 828606f2d42..314c676d7c0 100644
--- 
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMStateMachine.java
+++ 
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMStateMachine.java
@@ -17,15 +17,24 @@
 
 package org.apache.hadoop.hdds.scm.ha;
 
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import org.apache.hadoop.hdds.protocol.proto.SCMRatisProtocol.RequestType;
 import org.apache.hadoop.hdds.scm.container.placement.metrics.SCMMetrics;
+import org.apache.hadoop.hdds.scm.ha.invoker.ScmInvoker;
 import org.apache.hadoop.hdds.scm.server.StorageContainerManager;
 import org.apache.hadoop.hdds.utils.TransactionInfo;
+import org.apache.hadoop.ozone.upgrade.UpgradeException;
 import org.apache.ratis.proto.RaftProtos;
+import org.apache.ratis.proto.RaftProtos.LogEntryProto;
+import org.apache.ratis.proto.RaftProtos.StateMachineLogEntryProto;
 import org.apache.ratis.server.protocol.TermIndex;
+import org.apache.ratis.statemachine.TransactionContext;
+import org.apache.ratis.util.ExitUtils;
 import org.junit.jupiter.api.Test;
 
 /**
@@ -42,11 +51,56 @@ public void testRatisEventsRecording() throws Exception {
     SCMHADBTransactionBuffer buffer = mock(SCMHADBTransactionBuffer.class);
     
when(buffer.getLatestTrxInfo()).thenReturn(TransactionInfo.valueOf(TermIndex.valueOf(0,
 0)));
 
+    // The contents of the state machine are mocked, so SCMStateMachine#close 
is a no-op.
     SCMStateMachine stateMachine = new SCMStateMachine(scm, buffer);
-
     stateMachine.notifyConfigurationChanged(1, 1, 
RaftProtos.RaftConfigurationProto.getDefaultInstance());
+
     assertTrue(metrics.getRatisEvents().contains("Configuration changed at 
term index"));
 
     metrics.unRegister();
   }
+
+  /**
+   * A finalization step that throws an UpgradeException (an IOException, not 
an SCMException) must
+   * crash SCM rather than be returned to the Ratis client. UpgradeException 
skips the inner
+   * catch (SCMException) and hits the outer catch (Exception) -> 
ExitUtils.terminate.
+   */
+  @Test
+  public void testUpgradeExceptionDuringApplyTerminates() throws Exception {
+    ExitUtils.disableSystemExit();
+
+    StorageContainerManager scm = mock(StorageContainerManager.class);
+    SCMMetrics metrics = SCMMetrics.create();
+    when(scm.getMetrics()).thenReturn(metrics);
+
+    SCMHADBTransactionBuffer buffer = mock(SCMHADBTransactionBuffer.class);
+    
when(buffer.getLatestTrxInfo()).thenReturn(TransactionInfo.valueOf(TermIndex.valueOf(0,
 0)));
+
+    // The contents of the state machine are mocked, so SCMStateMachine#close 
is a no-op.
+    SCMStateMachine stateMachine = new SCMStateMachine(scm, buffer);
+    ScmInvoker<?> invoker = mock(ScmInvoker.class);
+    when(invoker.invokeLocal(any(), any())).thenThrow(
+        new 
UpgradeException(UpgradeException.ResultCodes.FINALIZE_UPGRADE_ACTION_FAILED));
+    stateMachine.registerInvoker(RequestType.FINALIZE, invoker);
+
+    SCMRatisRequest request = SCMRatisRequest.of(
+        RequestType.FINALIZE, "finalize", new Class<?>[]{});
+    StateMachineLogEntryProto smLogEntry = 
StateMachineLogEntryProto.newBuilder()
+        .setLogData(request.encode().getContent())
+        .build();
+    LogEntryProto logEntry = LogEntryProto.newBuilder()
+        .setTerm(1)
+        .setIndex(1)
+        .setStateMachineLogEntry(smLogEntry)
+        .build();
+    TransactionContext trx = mock(TransactionContext.class);
+    when(trx.getStateMachineLogEntry()).thenReturn(smLogEntry);
+    when(trx.getLogEntry()).thenReturn(logEntry);
+
+    // terminate throws ExitException when system exit is disabled
+    assertThrows(ExitUtils.ExitException.class,
+        () -> stateMachine.applyTransaction(trx));
+
+    metrics.unRegister();
+  }
 }
diff --git 
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/upgrade/TestClearFinalizingStateScmUpgradeAction.java
 
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/upgrade/TestClearFinalizingStateScmUpgradeAction.java
new file mode 100644
index 00000000000..d2ba6607226
--- /dev/null
+++ 
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/upgrade/TestClearFinalizingStateScmUpgradeAction.java
@@ -0,0 +1,83 @@
+/*
+ * 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.hdds.scm.server.upgrade;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.apache.hadoop.hdds.scm.ha.SCMHAManager;
+import org.apache.hadoop.hdds.scm.metadata.DBTransactionBuffer;
+import org.apache.hadoop.hdds.scm.metadata.SCMMetadataStore;
+import org.apache.hadoop.hdds.scm.server.StorageContainerManager;
+import org.apache.hadoop.hdds.utils.db.Table;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link ClearFinalizingStateScmUpgradeAction}.
+ */
+class TestClearFinalizingStateScmUpgradeAction {
+
+  private static final String LEGACY_FINALIZING_KEY = "#FINALIZING";
+
+  @Test
+  void testRemovesFinalizingKey() throws Exception {
+    @SuppressWarnings("unchecked")
+    Table<String, String> metaTable = mock(Table.class);
+    DBTransactionBuffer buffer = mock(DBTransactionBuffer.class);
+
+    SCMMetadataStore metadataStore = mock(SCMMetadataStore.class);
+    when(metadataStore.getMetaTable()).thenReturn(metaTable);
+
+    SCMHAManager haManager = mock(SCMHAManager.class);
+    when(haManager.getDBTransactionBuffer()).thenReturn(buffer);
+
+    StorageContainerManager scm = mock(StorageContainerManager.class);
+    when(scm.getScmMetadataStore()).thenReturn(metadataStore);
+    when(scm.getScmHAManager()).thenReturn(haManager);
+
+    new ClearFinalizingStateScmUpgradeAction().execute(scm);
+
+    verify(buffer).removeFromBuffer(metaTable, LEGACY_FINALIZING_KEY);
+  }
+
+  @Test
+  void testIdempotent() throws Exception {
+    // removeFromBuffer on an absent key is a no-op; call execute twice and 
expect no exception.
+    @SuppressWarnings("unchecked")
+    Table<String, String> metaTable = mock(Table.class);
+    DBTransactionBuffer buffer = mock(DBTransactionBuffer.class);
+
+    SCMMetadataStore metadataStore = mock(SCMMetadataStore.class);
+    when(metadataStore.getMetaTable()).thenReturn(metaTable);
+
+    SCMHAManager haManager = mock(SCMHAManager.class);
+    when(haManager.getDBTransactionBuffer()).thenReturn(buffer);
+
+    StorageContainerManager scm = mock(StorageContainerManager.class);
+    when(scm.getScmMetadataStore()).thenReturn(metadataStore);
+    when(scm.getScmHAManager()).thenReturn(haManager);
+
+    ClearFinalizingStateScmUpgradeAction action = new 
ClearFinalizingStateScmUpgradeAction();
+    action.execute(scm);
+    action.execute(scm);
+
+    // Called twice, once per execution.
+    verify(buffer, org.mockito.Mockito.times(2)).removeFromBuffer(metaTable, 
LEGACY_FINALIZING_KEY);
+  }
+}
diff --git 
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/upgrade/TestScmVersionManager.java
 
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/upgrade/TestScmVersionManager.java
index 61dcae5826d..5c32cd8f71b 100644
--- 
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/upgrade/TestScmVersionManager.java
+++ 
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/upgrade/TestScmVersionManager.java
@@ -140,7 +140,7 @@ public void testClasspathScanDiscoversUpgradeActions() 
throws Exception {
       ScmUpgradeAction upgradeAction = 
versionManager.getUpgradeActionsForTesting().get(DATANODE_SCHEMA_V2);
       assertInstanceOf(ScmOnFinalizeActionForDatanodeSchemaV2.class, 
upgradeAction);
       ScmUpgradeAction zduAction = 
versionManager.getUpgradeActionsForTesting().get(HDDSVersion.ZDU);
-      assertInstanceOf(ZduScmUpgradeActionForTest.class, zduAction);
+      assertInstanceOf(ClearFinalizingStateScmUpgradeAction.class, zduAction);
     }
 
     try (ScmVersionManager versionManager = 
createManager(HDDSVersion.SOFTWARE_VERSION.serialize(),
@@ -149,7 +149,7 @@ public void testClasspathScanDiscoversUpgradeActions() 
throws Exception {
       ScmUpgradeAction upgradeAction = 
versionManager.getUpgradeActionsForTesting().get(DATANODE_SCHEMA_V2);
       assertInstanceOf(ScmOnFinalizeActionForDatanodeSchemaV2.class, 
upgradeAction);
       ScmUpgradeAction zduAction = 
versionManager.getUpgradeActionsForTesting().get(HDDSVersion.ZDU);
-      assertInstanceOf(ZduScmUpgradeActionForTest.class, zduAction);
+      assertInstanceOf(ClearFinalizingStateScmUpgradeAction.class, zduAction);
     }
   }
 
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/upgrade/ClearPreparedStateOmUpgradeAction.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/upgrade/ClearPreparedStateOmUpgradeAction.java
new file mode 100644
index 00000000000..5ab4b093303
--- /dev/null
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/upgrade/ClearPreparedStateOmUpgradeAction.java
@@ -0,0 +1,60 @@
+/*
+ * 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.upgrade;
+
+import static org.apache.hadoop.ozone.OzoneManagerVersion.ZDU;
+
+import java.io.File;
+import java.io.IOException;
+import org.apache.hadoop.hdds.server.ServerUtils;
+import org.apache.hadoop.ozone.common.Storage;
+import org.apache.hadoop.ozone.om.OzoneManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Removes leftover OM "prepare for upgrade" state written by pre-ZDU code.
+ * It is idempotent (delete-if-exists), so it is a no-op on clusters that were 
never prepared and
+ * on fresh ZDU clusters that never finalize.
+ */
+@OmUpgradeActionForVersion(version = ZDU)
+public class ClearPreparedStateOmUpgradeAction implements OmUpgradeAction {
+  private static final Logger LOG = 
LoggerFactory.getLogger(ClearPreparedStateOmUpgradeAction.class);
+
+  // On-disk marker written by pre-ZDU OM prepare code; removed on ZDU 
finalization.
+  private static final String LEGACY_PREPARE_MARKER = "prepareMarker";
+  // transactionInfoTable key written by pre-ZDU OM prepare code.
+  private static final String LEGACY_PREPARE_MARKER_KEY = "#PREPAREDINFO";
+
+  @Override
+  public void execute(OzoneManager om) throws Exception {
+    // Reproduces the removed getPrepareMarkerFile() logic: <metadata 
dir>/current/prepareMarker.
+    File markerDir = new 
File(ServerUtils.getOzoneMetaDirPath(om.getConfiguration()),
+        Storage.STORAGE_DIR_CURRENT);
+    File marker = new File(markerDir, LEGACY_PREPARE_MARKER);
+    if (marker.exists()) {
+      if (!marker.delete()) {
+        throw new IOException("Failed to delete leftover OM prepare marker 
file " + marker);
+      }
+      LOG.info("Deleted leftover OM prepare marker file {}", marker);
+    }
+
+    // Direct RocksDB delete of the orphan prepare key, mirroring the removed 
startup cleanup.
+    
om.getMetadataManager().getTransactionInfoTable().delete(LEGACY_PREPARE_MARKER_KEY);
+  }
+}
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/upgrade/TestOMFinalizeUpgradeRequest.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/upgrade/TestOMFinalizeUpgradeRequest.java
index 2879a4e3d96..e282ce8dc0c 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/upgrade/TestOMFinalizeUpgradeRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/upgrade/TestOMFinalizeUpgradeRequest.java
@@ -18,6 +18,7 @@
 package org.apache.hadoop.ozone.om.request.upgrade;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
@@ -32,9 +33,14 @@
 import org.apache.hadoop.ozone.OzoneConsts;
 import org.apache.hadoop.ozone.OzoneManagerVersion;
 import org.apache.hadoop.ozone.om.execution.flowcontrol.ExecutionContext;
+import org.apache.hadoop.ozone.om.ratis.TestOzoneManagerStateMachine;
 import org.apache.hadoop.ozone.om.request.key.OMKeyRequestTests;
+import org.apache.hadoop.ozone.om.response.OMClientResponse;
 import org.apache.hadoop.ozone.om.upgrade.OMVersionManager;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Status;
+import org.apache.hadoop.ozone.upgrade.UpgradeException;
 import org.apache.hadoop.ozone.upgrade.UpgradeFinalization;
 import org.apache.ratis.protocol.ClientId;
 import org.apache.ratis.server.protocol.TermIndex;
@@ -71,6 +77,35 @@ public void testFinalizationInProgressKeyRemoved() throws 
IOException {
         "metric should be 0 after finalizing");
   }
 
+  /**
+   * A failed upgrade step (e.g. an upgrade action) surfaces as an 
UpgradeException,
+   * which is an IOException but not an OMException. exceptionToResponseStatus 
therefore
+   * maps it to INTERNAL_ERROR, and OzoneManagerStateMachine.processResponse 
terminates the
+   * process on INTERNAL_ERROR. The INTERNAL_ERROR -> terminate half of the 
chain is covered by
+   * {@link 
TestOzoneManagerStateMachine#testProcessResponseInternalErrorTerminates}.
+   */
+  @Test
+  public void testFinalizeFailureMapsToInternalError() throws IOException {
+    OMVersionManager omVersionManager = mock(OMVersionManager.class);
+    
when(omVersionManager.getApparentVersion()).thenReturn(OzoneManagerVersion.DEFAULT_VERSION);
+    when(ozoneManager.getVersionManager()).thenReturn(omVersionManager);
+    when(ozoneManager.finalizeUpgrade(any())).thenThrow(
+        new 
UpgradeException(UpgradeException.ResultCodes.FINALIZE_UPGRADE_ACTION_FAILED));
+
+    OzoneManagerProtocolProtos.OMRequest omRequest = 
OzoneManagerProtocolProtos.OMRequest.newBuilder()
+        .setCmdType(OzoneManagerProtocolProtos.Type.FinalizeUpgrade)
+        .setClientId(ClientId.randomId().toString())
+        .build();
+    OMFinalizeUpgradeRequest request = new OMFinalizeUpgradeRequest(omRequest);
+    ExecutionContext context = ExecutionContext.of(1, TermIndex.INITIAL_VALUE);
+    request.preExecute(ozoneManager);
+
+    OMClientResponse response = request.validateAndUpdateCache(ozoneManager, 
context);
+    OMResponse omResponse = response.getOMResponse();
+    assertFalse(omResponse.getSuccess());
+    assertEquals(Status.INTERNAL_ERROR, omResponse.getStatus());
+  }
+
   private void submitRequest() throws IOException {
     OzoneManagerProtocolProtos.OMRequest omRequest = 
OzoneManagerProtocolProtos.OMRequest.newBuilder()
         .setCmdType(OzoneManagerProtocolProtos.Type.FinalizeUpgrade)
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/TestClearPreparedStateOmUpgradeAction.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/TestClearPreparedStateOmUpgradeAction.java
new file mode 100644
index 00000000000..18ecd7f5030
--- /dev/null
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/TestClearPreparedStateOmUpgradeAction.java
@@ -0,0 +1,88 @@
+/*
+ * 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.upgrade;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.nio.file.Path;
+import org.apache.hadoop.hdds.HddsConfigKeys;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.utils.TransactionInfo;
+import org.apache.hadoop.hdds.utils.db.Table;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
+import org.apache.hadoop.ozone.om.OzoneManager;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+/**
+ * Unit tests for {@link ClearPreparedStateOmUpgradeAction}.
+ */
+class TestClearPreparedStateOmUpgradeAction {
+
+  private static final String PREPARE_MARKER_KEY = "#PREPAREDINFO";
+
+  @TempDir
+  private Path tempDir;
+
+  private OzoneManager mockOm(OzoneConfiguration conf) {
+    OMMetadataManager metadataManager = mock(OMMetadataManager.class);
+    @SuppressWarnings("unchecked")
+    Table<String, TransactionInfo> txTable = mock(Table.class);
+    when(metadataManager.getTransactionInfoTable()).thenReturn(txTable);
+
+    OzoneManager om = mock(OzoneManager.class);
+    when(om.getConfiguration()).thenReturn(conf);
+    when(om.getMetadataManager()).thenReturn(metadataManager);
+    return om;
+  }
+
+  @Test
+  void testDeletesMarkerFileAndDbKey() throws Exception {
+    OzoneConfiguration conf = new OzoneConfiguration();
+    conf.set(org.apache.hadoop.hdds.HddsConfigKeys.OZONE_METADATA_DIRS, 
tempDir.toString());
+
+    // Create the "current" dir and the marker file that pre-ZDU code would 
leave behind.
+    File currentDir = new File(tempDir.toFile(), "current");
+    assertTrue(currentDir.mkdirs());
+    File marker = new File(currentDir, "prepareMarker");
+    assertTrue(marker.createNewFile());
+
+    OzoneManager om = mockOm(conf);
+    new ClearPreparedStateOmUpgradeAction().execute(om);
+
+    assertFalse(marker.exists(), "prepare marker file should be deleted");
+    
verify(om.getMetadataManager().getTransactionInfoTable()).delete(PREPARE_MARKER_KEY);
+  }
+
+  @Test
+  void testIdempotentWhenNoMarkerPresent() throws Exception {
+    OzoneConfiguration conf = new OzoneConfiguration();
+    conf.set(HddsConfigKeys.OZONE_METADATA_DIRS, tempDir.toString());
+
+    OzoneManager om = mockOm(conf);
+    // Should succeed without error even though the marker file and DB key are 
absent.
+    new ClearPreparedStateOmUpgradeAction().execute(om);
+
+    
verify(om.getMetadataManager().getTransactionInfoTable()).delete(PREPARE_MARKER_KEY);
+  }
+}
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/TestOMVersionManager.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/TestOMVersionManager.java
index 009ff9f2020..5a927fa3614 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/TestOMVersionManager.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/TestOMVersionManager.java
@@ -133,7 +133,7 @@ public void testClasspathScanDiscoversUpgradeActions() 
throws Exception {
       OmUpgradeAction quotaAction = 
versionManager.getUpgradeActionsForTesting().get(QUOTA);
       assertInstanceOf(QuotaRepairUpgradeAction.class, quotaAction);
       OmUpgradeAction zduAction = 
versionManager.getUpgradeActionsForTesting().get(ZDU);
-      assertInstanceOf(ZduOmUpgradeActionForTest.class, zduAction);
+      assertInstanceOf(ClearPreparedStateOmUpgradeAction.class, zduAction);
     }
 
     try (OMVersionManager versionManager = 
createManager(SOFTWARE_VERSION.serialize(), new OMUpgradeActionProvider())) {
@@ -141,7 +141,7 @@ public void testClasspathScanDiscoversUpgradeActions() 
throws Exception {
       OmUpgradeAction quotaAction = 
versionManager.getUpgradeActionsForTesting().get(QUOTA);
       assertInstanceOf(QuotaRepairUpgradeAction.class, quotaAction);
       OmUpgradeAction zduAction = 
versionManager.getUpgradeActionsForTesting().get(ZDU);
-      assertInstanceOf(ZduOmUpgradeActionForTest.class, zduAction);
+      assertInstanceOf(ClearPreparedStateOmUpgradeAction.class, zduAction);
     }
   }
 
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/ZduOmUpgradeActionForTest.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/ZduOmUpgradeActionForTest.java
deleted file mode 100644
index b4609992651..00000000000
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/ZduOmUpgradeActionForTest.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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.upgrade;
-
-import static org.apache.hadoop.ozone.OzoneManagerVersion.ZDU;
-
-import org.apache.hadoop.ozone.om.OzoneManager;
-
-/**
- * No-op upgrade action used only to verify that {@link 
OmUpgradeActionForVersion} is scanned by
- * {@link OMUpgradeActionProvider} in tests.
- */
-@OmUpgradeActionForVersion(version = ZDU)
-public class ZduOmUpgradeActionForTest implements OmUpgradeAction {
-  @Override
-  public void execute(OzoneManager arg) {
-  }
-}


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

Reply via email to