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 f2a996cd6f6 HDDS-15376. Implement new ScmVersionManager. (#10367)
f2a996cd6f6 is described below

commit f2a996cd6f616678f284f83342bd7057368268fc
Author: Ethan Rose <[email protected]>
AuthorDate: Mon Jun 8 11:54:18 2026 -0400

    HDDS-15376. Implement new ScmVersionManager. (#10367)
    
    Co-authored-by: Cursor <[email protected]>
---
 .../container/upgrade/DatanodeVersionManager.java  |  10 +-
 ...DSVersionManager.java => HDDSVersionUtils.java} |  53 +++--
 .../ozone/upgrade/RatisBasedVersionManager.java    |  69 +++++++
 .../ScmOnFinalizeActionForDatanodeSchemaV2.java    |   9 +-
 .../hdds/scm/server/upgrade/ScmVersionManager.java |  80 ++++++++
 .../hadoop/hdds/upgrade/ScmUpgradeAction.java      |   4 +-
 .../scm/server/upgrade/TestScmVersionManager.java  | 224 +++++++++++++++++++++
 .../org/apache/hadoop/ozone/om/OzoneManager.java   |   4 +-
 .../hadoop/ozone/om/upgrade/OMVersionManager.java  |  43 +---
 9 files changed, 426 insertions(+), 70 deletions(-)

diff --git 
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/upgrade/DatanodeVersionManager.java
 
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/upgrade/DatanodeVersionManager.java
index 17f92c19bcb..ec763575c22 100644
--- 
a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/upgrade/DatanodeVersionManager.java
+++ 
b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/upgrade/DatanodeVersionManager.java
@@ -21,18 +21,20 @@
 import java.io.IOException;
 import java.util.Map;
 import org.apache.hadoop.hdds.ComponentVersion;
+import org.apache.hadoop.hdds.HDDSVersion;
 import org.apache.hadoop.hdds.upgrade.DatanodeUpgradeAction;
 import org.apache.hadoop.hdds.upgrade.DatanodeUpgradeActionProvider;
-import org.apache.hadoop.hdds.upgrade.HDDSVersionManager;
+import org.apache.hadoop.hdds.upgrade.HDDSVersionUtils;
 import org.apache.hadoop.ozone.container.common.DatanodeStorage;
 import 
org.apache.hadoop.ozone.container.common.statemachine.DatanodeStateMachine;
 import org.apache.hadoop.ozone.upgrade.ComponentUpgradeActionProvider;
+import org.apache.hadoop.ozone.upgrade.ComponentVersionManager;
 import org.apache.hadoop.ozone.upgrade.UpgradeException;
 
 /**
  * Datanode-specific version manager that wires upgrade actions internally.
  */
-public class DatanodeVersionManager extends HDDSVersionManager {
+public class DatanodeVersionManager extends ComponentVersionManager {
 
   private final Map<ComponentVersion, DatanodeUpgradeAction> upgradeActions;
   private final DatanodeStateMachine upgradeActionArg;
@@ -44,7 +46,9 @@ public DatanodeVersionManager(DatanodeStorage storage, 
DatanodeStateMachine upgr
   @VisibleForTesting
   public DatanodeVersionManager(DatanodeStorage storage, DatanodeStateMachine 
upgradeActionArg,
       ComponentUpgradeActionProvider<DatanodeUpgradeAction> 
upgradeActionProvider) throws IOException {
-    super(storage);
+    super(storage,
+        
HDDSVersionUtils.deserializedPersistedApparentVersion(storage.getApparentVersion()),
+        HDDSVersion.SOFTWARE_VERSION);
     this.upgradeActionArg = upgradeActionArg;
     upgradeActions = upgradeActionProvider.load();
   }
diff --git 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/upgrade/HDDSVersionManager.java
 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/upgrade/HDDSVersionUtils.java
similarity index 54%
rename from 
hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/upgrade/HDDSVersionManager.java
rename to 
hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/upgrade/HDDSVersionUtils.java
index d9dcb36e670..fa39b19ecb8 100644
--- 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/upgrade/HDDSVersionManager.java
+++ 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/upgrade/HDDSVersionUtils.java
@@ -20,43 +20,52 @@
 import java.io.IOException;
 import org.apache.hadoop.hdds.ComponentVersion;
 import org.apache.hadoop.hdds.HDDSVersion;
-import org.apache.hadoop.ozone.common.Storage;
-import org.apache.hadoop.ozone.upgrade.ComponentVersionManager;
-import org.apache.hadoop.ozone.upgrade.UpgradeException;
 
 /**
  * Component version manager for HDDS (Datanodes and SCM).
  */
-public abstract class HDDSVersionManager extends ComponentVersionManager {
-  protected HDDSVersionManager(Storage storage) throws IOException {
-    super(storage, computeApparentVersion(storage.getApparentVersion()), 
HDDSVersion.SOFTWARE_VERSION);
+public final class HDDSVersionUtils {
+  private HDDSVersionUtils() {
   }
 
   /**
    * If the apparent version stored on the disk is &gt;= {@link 
HDDSVersion#ZDU} serialized, the apparent version is
-   * resolved via {@link HDDSVersion#deserialize(int)}. Values with no 
matching {@link HDDSVersion} fail startup with
-   * the persisted integer in the exception message.
+   * resolved via {@link HDDSVersion#deserialize(int)}.
    * If the value is below that threshold, the apparent version is resolved as 
a {@link HDDSLayoutFeature}. Integers in
-   * the gap between the largest {@link HDDSLayoutFeature} and ZDU are not 
valid legacy layout values; startup fails
-   * with the persisted integer in the exception message.
+   * the gap between the largest {@link HDDSLayoutFeature} and ZDU are not 
valid legacy layout values.
+   *
+   * If the serialized version does not match any of these known versions, 
{@link HDDSVersion#UNKNOWN_VERSION} is
+   * returned.
    */
-  private static ComponentVersion computeApparentVersion(int 
serializedApparentVersion) throws IOException {
-    if (serializedApparentVersion >= HDDSVersion.ZDU.serialize()) {
-      HDDSVersion fromHdds = 
HDDSVersion.deserialize(serializedApparentVersion);
-      if (fromHdds != HDDSVersion.UNKNOWN_VERSION) {
-        return fromHdds;
-      }
+  public static ComponentVersion deserializeHDDSVersionOrLayoutVersion(int 
serializedVersion) {
+    if (serializedVersion >= HDDSVersion.ZDU.serialize()) {
+      return HDDSVersion.deserialize(serializedVersion);
     } else {
-      ComponentVersion fromLayout = 
HDDSLayoutFeature.deserialize(serializedApparentVersion);
+      ComponentVersion fromLayout = 
HDDSLayoutFeature.deserialize(serializedVersion);
       if (fromLayout != null) {
         return fromLayout;
+      } else {
+        return HDDSVersion.UNKNOWN_VERSION;
       }
     }
-    throw new IOException("Initialization failed. Disk contains unknown 
apparent version " + serializedApparentVersion +
-        " for software version " + HDDSVersion.SOFTWARE_VERSION + ". Make sure 
this component was not downgraded" +
-        " after finalization");
   }
 
-  @Override
-  protected abstract void runUpgradeAction(ComponentVersion version) throws 
UpgradeException;
+  /**
+   * If the apparent version stored on the disk is &gt;= {@link 
HDDSVersion#ZDU} serialized, the apparent version is
+   * resolved via {@link HDDSVersion#deserialize(int)}. Values with no 
matching {@link HDDSVersion} fail startup with
+   * the persisted integer in the exception message.
+   * If the value is below that threshold, the apparent version is resolved as 
a {@link HDDSLayoutFeature}. Integers in
+   * the gap between the largest {@link HDDSLayoutFeature} and ZDU are not 
valid legacy layout values; startup fails
+   * with the persisted integer in the exception message.
+   */
+  public static ComponentVersion deserializedPersistedApparentVersion(int 
serializedApparentVersion)
+      throws IOException {
+    ComponentVersion persistedVersion = 
deserializeHDDSVersionOrLayoutVersion(serializedApparentVersion);
+    if (persistedVersion == HDDSVersion.UNKNOWN_VERSION) {
+      throw new IOException("Initialization failed. Disk contains unknown 
apparent version "
+          + serializedApparentVersion + " for software version " + 
HDDSVersion.SOFTWARE_VERSION
+          + ". Make sure this component was not downgraded after 
finalization");
+    }
+    return persistedVersion;
+  }
 }
diff --git 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/upgrade/RatisBasedVersionManager.java
 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/upgrade/RatisBasedVersionManager.java
new file mode 100644
index 00000000000..74ba51a09da
--- /dev/null
+++ 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/upgrade/RatisBasedVersionManager.java
@@ -0,0 +1,69 @@
+/*
+ * 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.apache.hadoop.ozone.OzoneConsts.APPARENT_VERSION_KEY;
+
+import java.io.IOException;
+import org.apache.hadoop.hdds.ComponentVersion;
+import org.apache.hadoop.hdds.utils.db.Table;
+import org.apache.hadoop.ozone.common.Storage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Base version manager implementation for ratis-backed component versions.
+ */
+public abstract class RatisBasedVersionManager extends ComponentVersionManager 
{
+
+  private static final Logger LOG = 
LoggerFactory.getLogger(RatisBasedVersionManager.class);
+
+  protected RatisBasedVersionManager(Storage storage, ComponentVersion 
apparentVersion,
+      ComponentVersion softwareVersion) {
+    super(storage, apparentVersion, softwareVersion);
+  }
+
+  public void validateDBVersion(Table<String, String> finalizationStore) 
throws IOException {
+    ComponentVersion dbVersion = getApparentVersionInDB(finalizationStore);
+    ComponentVersion apparentVersion = getApparentVersion();
+
+    if (!apparentVersion.equals(dbVersion)) {
+      LOG.info("Version file has different apparent version ({}) than DB ({}). 
That is expected if this "
+          + "component has never been finalized to a newer version.", 
apparentVersion, dbVersion);
+    }
+  }
+
+  public void finalizeFromSnapshotIfRequired(Table<String, String> 
finalizationStore) throws IOException {
+    ComponentVersion apparentVersionInNewDB = 
getApparentVersionInDB(finalizationStore);
+    if (apparentVersionInNewDB != null && !isAllowed(apparentVersionInNewDB)) {
+      LOG.info("New snapshot received with higher apparent version {}. 
Attempting to finalize to that version.",
+          apparentVersionInNewDB);
+      finalizeUpgrade();
+      // Update the apparent version in the DB to match the VERSION file.
+      // When finalization is not done with a snapshot, this DB value is 
updated by OMFinalizeUpgradeRequest.
+      finalizationStore.put(APPARENT_VERSION_KEY, 
String.valueOf(getApparentVersion().serialize()));
+    }
+  }
+
+  protected abstract ComponentVersion computeApparentVersion(int 
serializedVersion) throws IOException;
+
+  private ComponentVersion getApparentVersionInDB(Table<String, String> 
finalizationStore) throws IOException {
+    String apparentVersion = finalizationStore.get(APPARENT_VERSION_KEY);
+    return (apparentVersion == null) ? null : 
computeApparentVersion(Integer.parseInt(apparentVersion));
+  }
+}
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/upgrade/ScmOnFinalizeActionForDatanodeSchemaV2.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/upgrade/ScmOnFinalizeActionForDatanodeSchemaV2.java
index bd62e87def7..d6b3cef1ce3 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/upgrade/ScmOnFinalizeActionForDatanodeSchemaV2.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/upgrade/ScmOnFinalizeActionForDatanodeSchemaV2.java
@@ -19,6 +19,7 @@
 
 import static 
org.apache.hadoop.hdds.upgrade.HDDSLayoutFeature.DATANODE_SCHEMA_V2;
 
+import org.apache.hadoop.hdds.scm.server.OzoneStorageContainerManager;
 import org.apache.hadoop.hdds.upgrade.ScmUpgradeAction;
 import org.apache.hadoop.ozone.upgrade.UpgradeActionScm;
 import org.slf4j.Logger;
@@ -28,13 +29,11 @@
  * SCM Upgrade Action for the very first Upgrade Version.
  */
 @UpgradeActionScm(feature = DATANODE_SCHEMA_V2)
-public class ScmOnFinalizeActionForDatanodeSchemaV2 implements
-    ScmUpgradeAction {
-  private static final Logger LOG =
-      LoggerFactory.getLogger(ScmOnFinalizeActionForDatanodeSchemaV2.class);
+public class ScmOnFinalizeActionForDatanodeSchemaV2 implements 
ScmUpgradeAction {
+  private static final Logger LOG = 
LoggerFactory.getLogger(ScmOnFinalizeActionForDatanodeSchemaV2.class);
 
   @Override
-  public void execute(SCMUpgradeFinalizationContext context) throws Exception {
+  public void execute(OzoneStorageContainerManager context) throws Exception {
     LOG.info("Executing SCM On Finalize action for layout feature {}",
         DATANODE_SCHEMA_V2);
   }
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/upgrade/ScmVersionManager.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/upgrade/ScmVersionManager.java
new file mode 100644
index 00000000000..7d28a332850
--- /dev/null
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/upgrade/ScmVersionManager.java
@@ -0,0 +1,80 @@
+/*
+ * 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 com.google.common.annotations.VisibleForTesting;
+import java.io.IOException;
+import java.util.Map;
+import org.apache.hadoop.hdds.ComponentVersion;
+import org.apache.hadoop.hdds.HDDSVersion;
+import org.apache.hadoop.hdds.scm.server.OzoneStorageContainerManager;
+import org.apache.hadoop.hdds.scm.server.SCMStorageConfig;
+import org.apache.hadoop.hdds.upgrade.HDDSVersionUtils;
+import org.apache.hadoop.hdds.upgrade.ScmUpgradeAction;
+import org.apache.hadoop.hdds.upgrade.ScmUpgradeActionProvider;
+import org.apache.hadoop.ozone.upgrade.ComponentUpgradeActionProvider;
+import org.apache.hadoop.ozone.upgrade.RatisBasedVersionManager;
+import org.apache.hadoop.ozone.upgrade.UpgradeException;
+
+/**
+ * SCM-specific version manager that wires upgrade actions internally.
+ */
+public class ScmVersionManager extends RatisBasedVersionManager {
+
+  private final Map<ComponentVersion, ScmUpgradeAction> upgradeActions;
+  private final OzoneStorageContainerManager upgradeActionArg;
+
+  public ScmVersionManager(SCMStorageConfig storage, 
OzoneStorageContainerManager upgradeActionArg) throws IOException {
+    this(storage, upgradeActionArg, new ScmUpgradeActionProvider());
+  }
+
+  @VisibleForTesting
+  public ScmVersionManager(SCMStorageConfig storage,
+      OzoneStorageContainerManager upgradeActionArg,
+      ComponentUpgradeActionProvider<ScmUpgradeAction> upgradeActionProvider)
+      throws IOException {
+    super(storage, 
HDDSVersionUtils.deserializedPersistedApparentVersion(storage.getApparentVersion()),
+        HDDSVersion.SOFTWARE_VERSION);
+    this.upgradeActionArg = upgradeActionArg;
+    upgradeActions = upgradeActionProvider.load();
+  }
+
+  @VisibleForTesting
+  public Map<ComponentVersion, ScmUpgradeAction> getUpgradeActionsForTesting() 
{
+    return upgradeActions;
+  }
+
+  @Override
+  protected void runUpgradeAction(ComponentVersion version) throws 
UpgradeException {
+    ScmUpgradeAction action = upgradeActions.get(version);
+    if (action == null) {
+      return;
+    }
+    try {
+      action.execute(upgradeActionArg);
+    } catch (Exception e) {
+      logAndThrow(e, "SCM upgrade action for version " + version + " failed.",
+          UpgradeException.ResultCodes.FINALIZE_UPGRADE_ACTION_FAILED);
+    }
+  }
+
+  @Override
+  protected ComponentVersion computeApparentVersion(int serializedVersion) 
throws IOException {
+    return 
HDDSVersionUtils.deserializedPersistedApparentVersion(serializedVersion);
+  }
+}
diff --git 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/upgrade/ScmUpgradeAction.java
 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/upgrade/ScmUpgradeAction.java
index 25fa5d3e9db..47cc02ed7ba 100644
--- 
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/upgrade/ScmUpgradeAction.java
+++ 
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/upgrade/ScmUpgradeAction.java
@@ -17,12 +17,12 @@
 
 package org.apache.hadoop.hdds.upgrade;
 
-import org.apache.hadoop.hdds.scm.server.upgrade.SCMUpgradeFinalizationContext;
+import org.apache.hadoop.hdds.scm.server.OzoneStorageContainerManager;
 import org.apache.hadoop.ozone.upgrade.UpgradeAction;
 
 /**
  * Storage Container Manager Upgrade Action interface. An upgrade action is an 
operation that
  * needs to be executed during finalization.
  */
-public interface ScmUpgradeAction extends 
UpgradeAction<SCMUpgradeFinalizationContext> {
+public interface ScmUpgradeAction extends 
UpgradeAction<OzoneStorageContainerManager> {
 }
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
new file mode 100644
index 00000000000..c112217f80e
--- /dev/null
+++ 
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/upgrade/TestScmVersionManager.java
@@ -0,0 +1,224 @@
+/*
+ * 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.apache.hadoop.hdds.upgrade.HDDSLayoutFeature.DATANODE_SCHEMA_V2;
+import static 
org.apache.hadoop.hdds.upgrade.HDDSLayoutFeature.ERASURE_CODED_STORAGE_SUPPORT;
+import static org.apache.hadoop.hdds.upgrade.HDDSLayoutFeature.INITIAL_VERSION;
+import static 
org.apache.hadoop.hdds.upgrade.HDDSLayoutFeature.STORAGE_SPACE_DISTRIBUTION;
+import static 
org.apache.hadoop.hdds.upgrade.HDDSLayoutFeature.WITNESSED_CONTAINER_DB_PROTO_VALUE;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+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.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Stream;
+import org.apache.hadoop.hdds.ComponentVersion;
+import org.apache.hadoop.hdds.HDDSVersion;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.scm.ScmConfigKeys;
+import org.apache.hadoop.hdds.scm.server.SCMStorageConfig;
+import org.apache.hadoop.hdds.scm.server.StorageContainerManager;
+import org.apache.hadoop.hdds.upgrade.HDDSLayoutFeature;
+import org.apache.hadoop.hdds.upgrade.ScmUpgradeAction;
+import org.apache.hadoop.hdds.upgrade.ScmUpgradeActionProvider;
+import org.apache.hadoop.ozone.upgrade.AbstractComponentVersionManagerTest;
+import org.apache.hadoop.ozone.upgrade.ComponentUpgradeActionProvider;
+import org.apache.hadoop.ozone.upgrade.ComponentVersionManager;
+import org.apache.hadoop.ozone.upgrade.UpgradeException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.provider.Arguments;
+
+/**
+ * Tests for {@link ScmVersionManager} using on-disk {@link SCMStorageConfig}
+ * under a JUnit temp directory.
+ */
+class TestScmVersionManager extends AbstractComponentVersionManagerTest {
+
+  private OzoneConfiguration conf;
+
+  @TempDir
+  private Path tempDir;
+
+  private static final List<ComponentVersion> ALL_VERSIONS;
+
+  static {
+    ALL_VERSIONS = new ArrayList<>(Arrays.asList(HDDSLayoutFeature.values()));
+    for (HDDSVersion version : HDDSVersion.values()) {
+      // Add all defined versions after and including ZDU to get the complete 
version list.
+      if (HDDSVersion.ZDU.isSupportedBy(version) && version != 
HDDSVersion.UNKNOWN_VERSION) {
+        ALL_VERSIONS.add(version);
+      }
+    }
+  }
+
+  @BeforeEach
+  public void init() {
+    conf = new OzoneConfiguration();
+  }
+
+  public static Stream<Arguments> preFinalizedVersionArgs() {
+    return ALL_VERSIONS.stream()
+        .limit(ALL_VERSIONS.size() - 1)
+        .map(org.junit.jupiter.params.provider.Arguments::of);
+  }
+
+  @Override
+  protected ComponentVersionManager createManager(int 
serializedApparentVersion) throws IOException {
+    return createManager(serializedApparentVersion, HashMap::new);
+  }
+
+  private ScmVersionManager createManager(int serializedApparentVersion,
+      ComponentUpgradeActionProvider<ScmUpgradeAction> actions) throws 
IOException {
+    SCMStorageConfig storage = newScmStorage(serializedApparentVersion);
+    StorageContainerManager context = mock(StorageContainerManager.class);
+    return new ScmVersionManager(storage, context, actions);
+  }
+
+  private SCMStorageConfig newScmStorage(int apparentVersion) throws 
IOException {
+    Path storageRoot = Files.createTempDirectory(tempDir, 
"scm-version-manager-");
+    conf.set(ScmConfigKeys.OZONE_SCM_DB_DIRS, storageRoot.toString());
+    SCMStorageConfig storage = new SCMStorageConfig(conf);
+    storage.setScmId("test-scm");
+    storage.setApparentVersion(apparentVersion);
+    storage.initialize();
+    return storage;
+  }
+
+  @Override
+  protected List<ComponentVersion> allVersionsInOrder() {
+    return ALL_VERSIONS;
+  }
+
+  @Override
+  protected ComponentVersion expectedSoftwareVersion() {
+    return HDDSVersion.SOFTWARE_VERSION;
+  }
+
+  @Override
+  @Test
+  public void testClasspathScanDiscoversUpgradeActions() throws Exception {
+    try (ScmVersionManager versionManager = 
createManager(INITIAL_VERSION.serialize(),
+        new ScmUpgradeActionProvider())) {
+      assertTrue(versionManager.needsFinalization());
+      ScmUpgradeAction upgradeAction = 
versionManager.getUpgradeActionsForTesting().get(DATANODE_SCHEMA_V2);
+      assertInstanceOf(ScmOnFinalizeActionForDatanodeSchemaV2.class, 
upgradeAction);
+    }
+
+    try (ScmVersionManager versionManager = 
createManager(HDDSVersion.SOFTWARE_VERSION.serialize(),
+        new ScmUpgradeActionProvider())) {
+      assertFalse(versionManager.needsFinalization());
+      ScmUpgradeAction upgradeAction = 
versionManager.getUpgradeActionsForTesting().get(DATANODE_SCHEMA_V2);
+      assertInstanceOf(ScmOnFinalizeActionForDatanodeSchemaV2.class, 
upgradeAction);
+    }
+  }
+
+  @Override
+  @Test
+  public void testFinalizeRunsSuppliedUpgradeAction() throws Exception {
+    ScmUpgradeAction mockECAction = mock(ScmUpgradeAction.class);
+    ScmUpgradeAction mockZDUAction = mock(ScmUpgradeAction.class);
+
+    ComponentUpgradeActionProvider<ScmUpgradeAction> provider = () -> {
+      Map<ComponentVersion, ScmUpgradeAction> m = new HashMap<>();
+      m.put(ERASURE_CODED_STORAGE_SUPPORT, mockECAction);
+      m.put(HDDSVersion.ZDU, mockZDUAction);
+      return m;
+    };
+
+    try (ScmVersionManager versionManager = 
createManager(ERASURE_CODED_STORAGE_SUPPORT.serialize(), provider)) {
+      versionManager.finalizeUpgrade();
+      assertEquals(HDDSVersion.SOFTWARE_VERSION, 
versionManager.getApparentVersion());
+
+      // Apparent version is already EC; finalization runs actions for later 
versions only, not for EC itself.
+      verify(mockECAction, never()).execute(any());
+      verify(mockZDUAction, atLeastOnce()).execute(any());
+      assertScmApparentVersionOnDisk(conf, 
HDDSVersion.SOFTWARE_VERSION.serialize());
+    }
+  }
+
+  @Override
+  @Test
+  public void testUpgradeActionFailureAbortsFinalize() throws Exception {
+    ComponentUpgradeActionProvider<ScmUpgradeAction> provider = () -> {
+      Map<ComponentVersion, ScmUpgradeAction> m = new HashMap<>();
+      m.put(STORAGE_SPACE_DISTRIBUTION, o -> {
+        throw new IOException("expected test failure");
+      });
+      return m;
+    };
+
+    try (ScmVersionManager versionManager =
+             createManager(WITNESSED_CONTAINER_DB_PROTO_VALUE.serialize(), 
provider)) {
+      UpgradeException thrown = assertThrows(UpgradeException.class, 
versionManager::finalizeUpgrade);
+      
assertEquals(UpgradeException.ResultCodes.FINALIZE_UPGRADE_ACTION_FAILED, 
thrown.getResult());
+      // WITNESSED_CONTAINER_DB_PROTO_VALUE is the version before 
STORAGE_SPACE_DISTRIBUTION, which has failed.
+      assertEquals(WITNESSED_CONTAINER_DB_PROTO_VALUE, 
versionManager.getApparentVersion());
+      assertScmApparentVersionOnDisk(conf, 
WITNESSED_CONTAINER_DB_PROTO_VALUE.serialize());
+    }
+  }
+
+  @Override
+  @Test
+  public void testPersistFailureRollsBack() throws Exception {
+    SCMStorageConfig storage = mock(SCMStorageConfig.class);
+    AtomicInteger persistedApparentVersion = new 
AtomicInteger(INITIAL_VERSION.serialize());
+    when(storage.getApparentVersion()).thenAnswer(invocation -> 
persistedApparentVersion.get());
+    doAnswer(invocation -> {
+      persistedApparentVersion.set(invocation.getArgument(0));
+      return null;
+    }).when(storage).setApparentVersion(anyInt());
+    doThrow(new IOException("persist 
failed")).when(storage).persistCurrentState();
+
+    StorageContainerManager context = mock(StorageContainerManager.class);
+    try (ScmVersionManager versionManager = new ScmVersionManager(storage, 
context, HashMap::new)) {
+      assertEquals(INITIAL_VERSION, versionManager.getApparentVersion());
+      UpgradeException thrown = assertThrows(UpgradeException.class, 
versionManager::finalizeUpgrade);
+      
assertEquals(UpgradeException.ResultCodes.APPARENT_VERSION_UPDATE_FAILED, 
thrown.getResult());
+      assertEquals(INITIAL_VERSION, versionManager.getApparentVersion());
+      assertEquals(INITIAL_VERSION.serialize(), storage.getApparentVersion());
+    }
+  }
+
+  private static void assertScmApparentVersionOnDisk(OzoneConfiguration conf, 
int expected)
+      throws IOException {
+    SCMStorageConfig reloaded = new SCMStorageConfig(conf);
+    assertEquals(expected, reloaded.getApparentVersion());
+  }
+}
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 b17eed0bbe3..04336f659db 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
@@ -954,7 +954,7 @@ private void instantiateServices(boolean withNewSnapshot) 
throws IOException {
     OmMetadataManagerImpl metadataManagerImpl =
         new OmMetadataManagerImpl(configuration, this);
     this.metadataManager = metadataManagerImpl;
-    versionManager.validateDBVersion(metadataManager);
+    versionManager.validateDBVersion(metadataManager.getMetaTable());
     LOG.info("S3 Multi-Tenancy is {}",
         isS3MultiTenancyEnabled ? "enabled" : "disabled");
     if (isS3MultiTenancyEnabled) {
@@ -1041,7 +1041,7 @@ public void close() {
     updateActiveSnapshotMetrics();
 
     if (withNewSnapshot) {
-      versionManager.finalizeFromSnapshotIfRequired(metadataManager);
+      
versionManager.finalizeFromSnapshotIfRequired(metadataManager.getMetaTable());
       instantiatePrepareStateAfterSnapshot();
     } else {
       // Prepare state depends on the transaction ID of metadataManager after a
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/upgrade/OMVersionManager.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/upgrade/OMVersionManager.java
index fffc76f5322..4d6547792c0 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/upgrade/OMVersionManager.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/upgrade/OMVersionManager.java
@@ -17,28 +17,21 @@
 
 package org.apache.hadoop.ozone.om.upgrade;
 
-import static org.apache.hadoop.ozone.OzoneConsts.APPARENT_VERSION_KEY;
-
 import com.google.common.annotations.VisibleForTesting;
 import java.io.IOException;
 import java.util.Map;
 import org.apache.hadoop.hdds.ComponentVersion;
 import org.apache.hadoop.ozone.OzoneManagerVersion;
-import org.apache.hadoop.ozone.om.OMMetadataManager;
 import org.apache.hadoop.ozone.om.OMStorage;
 import org.apache.hadoop.ozone.om.OzoneManager;
 import org.apache.hadoop.ozone.upgrade.ComponentUpgradeActionProvider;
-import org.apache.hadoop.ozone.upgrade.ComponentVersionManager;
+import org.apache.hadoop.ozone.upgrade.RatisBasedVersionManager;
 import org.apache.hadoop.ozone.upgrade.UpgradeException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * Component version manager for Ozone Manager.
  */
-public class OMVersionManager extends ComponentVersionManager {
-
-  private static final Logger LOG = 
LoggerFactory.getLogger(OMVersionManager.class);
+public class OMVersionManager extends RatisBasedVersionManager {
 
   private final Map<ComponentVersion, OmUpgradeAction> upgradeActions;
 
@@ -53,33 +46,11 @@ public OMVersionManager(OMStorage storage, OzoneManager 
upgradeActionArg) throws
   @VisibleForTesting
   public OMVersionManager(OMStorage storage, OzoneManager upgradeActionArg,
       ComponentUpgradeActionProvider<OmUpgradeAction> upgradeActionProvider) 
throws IOException {
-    super(storage, computeApparentVersion(storage.getApparentVersion()), 
OzoneManagerVersion.SOFTWARE_VERSION);
+    super(storage, 
computeApparentVersionInternal(storage.getApparentVersion()), 
OzoneManagerVersion.SOFTWARE_VERSION);
     this.upgradeActionArg = upgradeActionArg;
     upgradeActions = upgradeActionProvider.load();
   }
 
-  public void validateDBVersion(OMMetadataManager metadataManager) throws 
IOException {
-    ComponentVersion dbVersion = getApparentVersionInDB(metadataManager);
-    ComponentVersion apparentVersion = getApparentVersion();
-
-    if (!apparentVersion.equals(dbVersion)) {
-      LOG.info("Version file has different apparent version ({}) than OM DB 
({}). That is expected if this "
-          + "OM has never been finalized to a newer version.", 
apparentVersion, dbVersion);
-    }
-  }
-
-  public void finalizeFromSnapshotIfRequired(OMMetadataManager 
metadataManager) throws IOException {
-    ComponentVersion apparentVersionInDB = 
getApparentVersionInDB(metadataManager);
-    if (apparentVersionInDB != null && !isAllowed(apparentVersionInDB)) {
-      LOG.info("New OM snapshot received with higher apparent version {}. "
-          + "Attempting to finalize current OM to that version.", 
apparentVersionInDB);
-      finalizeUpgrade();
-      // Update the apparent version in the DB to match the VERSION file.
-      // When finalization is not done with a snapshot, this DB value is 
updated by OMFinalizeUpgradeRequest.
-      metadataManager.getMetaTable().put(APPARENT_VERSION_KEY, 
String.valueOf(getApparentVersion().serialize()));
-    }
-  }
-
   @VisibleForTesting
   public Map<ComponentVersion, OmUpgradeAction> getUpgradeActionsForTesting() {
     return upgradeActions;
@@ -99,9 +70,9 @@ protected void runUpgradeAction(ComponentVersion 
componentVersion) throws Upgrad
     }
   }
 
-  private static ComponentVersion getApparentVersionInDB(OMMetadataManager 
metadataManager) throws IOException {
-    String apparentVersion = 
metadataManager.getMetaTable().get(APPARENT_VERSION_KEY);
-    return (apparentVersion == null) ? null : 
computeApparentVersion(Integer.parseInt(apparentVersion));
+  @Override
+  protected ComponentVersion computeApparentVersion(int 
serializedApparentVersion) throws IOException {
+    return computeApparentVersionInternal(serializedApparentVersion);
   }
 
   /**
@@ -113,7 +84,7 @@ private static ComponentVersion 
getApparentVersionInDB(OMMetadataManager metadat
    * the gap between the largest {@link OMLayoutFeature} and ZDU are not valid 
legacy layout values; startup fails with
    * the persisted integer in the exception message.
    */
-  private static ComponentVersion computeApparentVersion(int 
serializedApparentVersion) throws IOException {
+  private static ComponentVersion computeApparentVersionInternal(int 
serializedApparentVersion) throws IOException {
     if (serializedApparentVersion >= OzoneManagerVersion.ZDU.serialize()) {
       OzoneManagerVersion fromOm = 
OzoneManagerVersion.deserialize(serializedApparentVersion);
       if (fromOm != OzoneManagerVersion.UNKNOWN_VERSION) {


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


Reply via email to