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 upgradeActions; private final DatanodeStateMachine upgradeActionArg; @@ -44,7 +46,9 @@ public DatanodeVersionManager(DatanodeStorage storage, DatanodeStateMachine upgr @VisibleForTesting public DatanodeVersionManager(DatanodeStorage storage, DatanodeStateMachine upgradeActionArg, ComponentUpgradeActionProvider 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 >= {@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 >= {@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 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 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 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 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 upgradeActionProvider) + throws IOException { + super(storage, HDDSVersionUtils.deserializedPersistedApparentVersion(storage.getApparentVersion()), + HDDSVersion.SOFTWARE_VERSION); + this.upgradeActionArg = upgradeActionArg; + upgradeActions = upgradeActionProvider.load(); + } + + @VisibleForTesting + public Map 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 { +public interface ScmUpgradeAction extends UpgradeAction { } 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 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 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 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 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 provider = () -> { + Map 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 provider = () -> { + Map 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 f89dae3b3c0..c4d54db6d4c 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 @@ -946,7 +946,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) { @@ -1033,7 +1033,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 upgradeActions; @@ -53,33 +46,11 @@ public OMVersionManager(OMStorage storage, OzoneManager upgradeActionArg) throws @VisibleForTesting public OMVersionManager(OMStorage storage, OzoneManager upgradeActionArg, ComponentUpgradeActionProvider 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 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) {