diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HDDSVersion.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HDDSVersion.java index 2cd2720d720f..b9de634c48e8 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HDDSVersion.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HDDSVersion.java @@ -25,7 +25,7 @@ import java.util.TreeMap; /** - * Versioning for datanode. + * Upgrade and downgrade version handling for SCM and Datanode. */ public enum HDDSVersion implements ComponentVersion { diff --git a/hadoop-hdds/docs/content/design/upgrade-dev-primer.md b/hadoop-hdds/docs/content/design/upgrade-dev-primer.md index 4daf13a097f5..b19199bbbc22 100644 --- a/hadoop-hdds/docs/content/design/upgrade-dev-primer.md +++ b/hadoop-hdds/docs/content/design/upgrade-dev-primer.md @@ -44,13 +44,15 @@ Class to add a new layout feature being brought in. Layout version is typically **DataNode** uses [`org.apache.hadoop.ozone.container.upgrade.DatanodeVersionManager`](https://github.com/apache/ozone/blob/master/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/upgrade/DatanodeVersionManager.java) with upgrade actions via [`org.apache.hadoop.ozone.container.upgrade.DatanodeUpgradeActionProvider`](https://github.com/apache/ozone/blob/master/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/upgrade/DatanodeUpgradeActionProvider.java). Both SCM and DataNode expose apparent/software `ComponentVersion` and `isAllowed(ComponentVersion)` for gating (including legacy [`HDDSLayoutFeature`](https://github.com/apache/ozone/blob/master/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/upgrade/HDDSLayoutFeature.java) checks where still used). +**Recon** uses [`org.apache.hadoop.ozone.recon.upgrade.ReconVersionManager`](https://github.com/apache/ozone/blob/master/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconVersionManager.java) for Derby SQL schema versioning, with versions defined in [`ReconVersion`](https://github.com/apache/ozone/blob/master/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconVersion.java) and upgrade actions via [`ReconUpgradeActionProvider`](https://github.com/apache/ozone/blob/master/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconUpgradeActionProvider.java). Apparent version is stored in `RECON_SCHEMA_VERSION`. Recon also runs [`ScmVersionManager`](https://github.com/apache/ozone/blob/master/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/upgrade/ScmVersionManager.java) for SCM-lite metadata and finalizes both tracks on startup. + ## @DisallowedUntilLayoutVersion Annotation Method level annotation used to "disallow" an API if current layout version does not include the associated layout feature. Currently it is added only to the OM module, but can easily be moved down to a common module based on need on the HDDS layer. ## @BelongsToLayoutVersion Annotation Annotation to mark an OM request class that it belongs to a specific Layout Version. Until that version is available post finalize, this request will not be supported. A newer version of an existing OM request can be created (by inheritance or a fully new class) and marked with a newer layout version. Until finalizing this layout version, the older request class is used. Post finalizing, the newer version of the request class is used. -## Upgrade Action (UpgradeActionOm, UpgradeActionScm & UpgradeActionDatanode) +## Upgrade Action (UpgradeActionOm, UpgradeActionScm, UpgradeActionDatanode & UpgradeActionRecon) Annotation to specify upgrade action run during finalization. Each layout feature can optionally define a single upgrade action that will be executed when the feature is finalized. This action should be idempotent and execute quickly. The action must complete for the feature to finish finalizing, so if there is an error executing the action it will be retried. This partial failure should not leave the component inoperable. diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/upgrade/ComponentVersionManager.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/upgrade/ComponentVersionManager.java index 5ab47c4e7f17..516158f76c65 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/upgrade/ComponentVersionManager.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/upgrade/ComponentVersionManager.java @@ -99,7 +99,6 @@ public void finalizeUpgrade() throws UpgradeException { LOG.info("Version {} has been finalized.", newVersion); } - LOG.info("Finalization is complete."); } /** 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 index 18e5dbe31794..c5a44fb249d1 100644 --- 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 @@ -44,7 +44,7 @@ public ScmVersionManager(SCMStorageConfig storage, OzoneStorageContainerManager this(storage, upgradeActionArg, new ScmUpgradeActionProvider()); } - @VisibleForTesting + // Used by Recon's node manager to track Datanode versions without running SCM specific upgrade actions. public ScmVersionManager(SCMStorageConfig storage, OzoneStorageContainerManager upgradeActionArg, ComponentUpgradeActionProvider upgradeActionProvider) diff --git a/hadoop-ozone/recon-codegen/src/main/java/org/apache/ozone/recon/schema/SchemaVersionTableDefinition.java b/hadoop-ozone/recon-codegen/src/main/java/org/apache/ozone/recon/schema/SchemaVersionTableDefinition.java index 1ee831963ada..59de2845e48c 100644 --- a/hadoop-ozone/recon-codegen/src/main/java/org/apache/ozone/recon/schema/SchemaVersionTableDefinition.java +++ b/hadoop-ozone/recon-codegen/src/main/java/org/apache/ozone/recon/schema/SchemaVersionTableDefinition.java @@ -42,7 +42,7 @@ public class SchemaVersionTableDefinition implements ReconSchemaDefinition { public static final String SCHEMA_VERSION_TABLE_NAME = "RECON_SCHEMA_VERSION"; private final DataSource dataSource; - private int latestSLV; + private int softwareVersion; @Inject public SchemaVersionTableDefinition(DataSource dataSource) { @@ -61,8 +61,7 @@ public void initializeSchema() throws SQLException { createSchemaVersionTable(localDslContext); if (isFreshInstall) { - // Fresh install: Set the SLV to the latest version - insertInitialSLV(localDslContext, latestSLV); + insertApparentVersion(localDslContext, softwareVersion); } } } @@ -80,27 +79,16 @@ private void createSchemaVersionTable(DSLContext dslContext) { .execute(); } - /** - * Inserts the initial SLV into the Schema Version table. - * - * @param dslContext The DSLContext to use for the operation. - * @param slv The initial SLV value. - */ - private void insertInitialSLV(DSLContext dslContext, int slv) { + private void insertApparentVersion(DSLContext dslContext, int apparentVersion) { dslContext.insertInto(DSL.table(SCHEMA_VERSION_TABLE_NAME)) .columns(DSL.field(name("version_number")), DSL.field(name("applied_on"))) - .values(slv, DSL.currentTimestamp()) + .values(apparentVersion, DSL.currentTimestamp()) .execute(); - LOG.info("Inserted initial SLV '{}' into SchemaVersion table.", slv); + LOG.info("Inserted initial apparent version '{}' into SchemaVersion table.", apparentVersion); } - /** - * Set the latest SLV. - * - * @param slv The latest Software Layout Version. - */ - public void setLatestSLV(int slv) { - this.latestSLV = slv; + public void setSoftwareVersion(int version) { + this.softwareVersion = version; } } diff --git a/hadoop-ozone/recon/pom.xml b/hadoop-ozone/recon/pom.xml index 9dacdf1f8ca1..bf34549a7561 100644 --- a/hadoop-ozone/recon/pom.xml +++ b/hadoop-ozone/recon/pom.xml @@ -296,6 +296,12 @@ test-jar test + + org.apache.ozone + hdds-server-framework + test-jar + test + org.apache.ozone hdds-server-scm diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconSchemaManager.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconSchemaManager.java index 724b15c96a95..dbfa66551cf0 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconSchemaManager.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconSchemaManager.java @@ -22,7 +22,7 @@ import java.sql.SQLException; import java.util.HashSet; import java.util.Set; -import org.apache.hadoop.ozone.recon.upgrade.ReconLayoutFeature; +import org.apache.hadoop.ozone.recon.upgrade.ReconVersion; import org.apache.ozone.recon.schema.ReconSchemaDefinition; import org.apache.ozone.recon.schema.SchemaVersionTableDefinition; import org.slf4j.Logger; @@ -44,9 +44,6 @@ public ReconSchemaManager(Set reconSchemaDefinitions) { @VisibleForTesting public void createReconSchema() { - // Calculate the latest SLV from ReconLayoutFeature - int latestSLV = calculateLatestSLV(); - try { // Initialize the schema version table first reconSchemaDefinitions.stream() @@ -54,7 +51,7 @@ public void createReconSchema() { .findFirst() .ifPresent(schemaDefinition -> { SchemaVersionTableDefinition schemaVersionTable = (SchemaVersionTableDefinition) schemaDefinition; - schemaVersionTable.setLatestSLV(latestSLV); + schemaVersionTable.setSoftwareVersion(ReconVersion.SOFTWARE_VERSION.serialize()); try { schemaVersionTable.initializeSchema(); } catch (SQLException e) { @@ -77,13 +74,4 @@ public void createReconSchema() { LOG.error("Error creating Recon schema.", e); } } - - /** - * Calculate the latest SLV by iterating over ReconLayoutFeature. - * - * @return The latest SLV. - */ - private int calculateLatestSLV() { - return ReconLayoutFeature.determineSLV(); - } } diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconSchemaVersionTableManager.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconSchemaVersionTableManager.java deleted file mode 100644 index 06d55b0a1c85..000000000000 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconSchemaVersionTableManager.java +++ /dev/null @@ -1,102 +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.recon; - -import static org.jooq.impl.DSL.name; - -import com.google.inject.Inject; -import java.sql.Connection; -import java.sql.SQLException; -import javax.sql.DataSource; -import org.jooq.DSLContext; -import org.jooq.impl.DSL; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Manager for handling the Recon Schema Version table. - * This class provides methods to get and update the current schema version. - */ -public class ReconSchemaVersionTableManager { - - private static final Logger LOG = LoggerFactory.getLogger(ReconSchemaVersionTableManager.class); - public static final String RECON_SCHEMA_VERSION_TABLE_NAME = "RECON_SCHEMA_VERSION"; - private DSLContext dslContext; - private final DataSource dataSource; - - @Inject - public ReconSchemaVersionTableManager(DataSource dataSource) throws SQLException { - this.dataSource = dataSource; - this.dslContext = DSL.using(dataSource.getConnection()); - } - - /** - * Get the current schema version from the RECON_SCHEMA_VERSION table. - * If the table is empty, or if it does not exist, it will return 0. - * @return The current schema version. - */ - public int getCurrentSchemaVersion() throws SQLException { - try { - return dslContext.select(DSL.field(name("version_number"))) - .from(DSL.table(RECON_SCHEMA_VERSION_TABLE_NAME)) - .fetchOptional() - .map(record -> record.get( - DSL.field(name("version_number"), Integer.class))) - .orElse(-1); // Return -1 if no version is found - } catch (Exception e) { - LOG.error("Failed to fetch the current schema version.", e); - throw new SQLException("Unable to read schema version from the table.", e); - } - } - - /** - * Update the schema version in the RECON_SCHEMA_VERSION table after all tables are upgraded. - * - * @param newVersion The new version to set. - */ - public void updateSchemaVersion(int newVersion, Connection conn) { - dslContext = DSL.using(conn); - boolean recordExists = dslContext.fetchExists(dslContext.selectOne() - .from(DSL.table(RECON_SCHEMA_VERSION_TABLE_NAME))); - - if (recordExists) { - // Update the existing schema version record - dslContext.update(DSL.table(RECON_SCHEMA_VERSION_TABLE_NAME)) - .set(DSL.field(name("version_number")), newVersion) - .set(DSL.field(name("applied_on")), DSL.currentTimestamp()) - .execute(); - LOG.info("Updated schema version to '{}'.", newVersion); - } else { - // Insert a new schema version record - dslContext.insertInto(DSL.table(RECON_SCHEMA_VERSION_TABLE_NAME)) - .columns(DSL.field(name("version_number")), - DSL.field(name("applied_on"))) - .values(newVersion, DSL.currentTimestamp()) - .execute(); - LOG.info("Inserted new schema version '{}'.", newVersion); - } - } - - /** - * Provides the data source used by this manager. - * @return The DataSource instance. - */ - public DataSource getDataSource() { - return dataSource; - } -} diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconServer.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconServer.java index 78a4938166a3..1c2e0325f4cd 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconServer.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconServer.java @@ -34,7 +34,6 @@ import java.util.Collection; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicBoolean; -import javax.sql.DataSource; import org.apache.hadoop.hdds.cli.GenericCli; import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.hadoop.hdds.protocolPB.SCMSecurityProtocolClientSideTranslatorPB; @@ -50,6 +49,7 @@ import org.apache.hadoop.ozone.recon.metrics.ReconTaskStatusMetrics; import org.apache.hadoop.ozone.recon.scm.ReconSafeModeManager; import org.apache.hadoop.ozone.recon.scm.ReconStorageConfig; +import org.apache.hadoop.ozone.recon.scm.ReconStorageContainerManagerFacade; import org.apache.hadoop.ozone.recon.security.ReconCertificateClient; import org.apache.hadoop.ozone.recon.spi.OzoneManagerServiceProvider; import org.apache.hadoop.ozone.recon.spi.ReconContainerMetadataManager; @@ -57,7 +57,8 @@ import org.apache.hadoop.ozone.recon.spi.StorageContainerServiceProvider; import org.apache.hadoop.ozone.recon.spi.impl.ReconDBProvider; import org.apache.hadoop.ozone.recon.tasks.ReconTaskController; -import org.apache.hadoop.ozone.recon.upgrade.ReconLayoutVersionManager; +import org.apache.hadoop.ozone.recon.upgrade.ReconVersionManager; +import org.apache.hadoop.ozone.upgrade.UpgradeException; import org.apache.hadoop.ozone.util.OzoneNetUtils; import org.apache.hadoop.ozone.util.OzoneVersionInfo; import org.apache.hadoop.ozone.util.ShutdownHookManager; @@ -83,7 +84,7 @@ public class ReconServer extends GenericCli implements Callable { private OzoneManagerServiceProvider ozoneManagerServiceProvider; private ReconDBProvider reconDBProvider; private ReconNamespaceSummaryManager reconNamespaceSummaryManager; - private OzoneStorageContainerManager reconStorageContainerManager; + private ReconStorageContainerManagerFacade reconStorageContainerManager; private OzoneConfiguration configuration; private ReconStorageConfig reconStorage; private CertificateClient certClient; @@ -163,8 +164,7 @@ public Void call() throws Exception { httpServer = injector.getInstance(ReconHttpServer.class); this.ozoneManagerServiceProvider = injector.getInstance(OzoneManagerServiceProvider.class); - this.reconStorageContainerManager = - injector.getInstance(OzoneStorageContainerManager.class); + this.reconStorageContainerManager = injector.getInstance(ReconStorageContainerManagerFacade.class); this.reconTaskStatusMetrics = injector.getInstance(ReconTaskStatusMetrics.class); @@ -172,24 +172,13 @@ public Void call() throws Exception { LOG.info("Initializing support of Recon Features..."); FeatureProvider.initFeatureSupport(configuration); + finalizeUpgrade(); + LOG.debug("Now starting all services of Recon..."); // Start all services start(); isStarted = true; - LOG.info("Finalizing Layout Features."); - // Handle Recon Schema Versioning - ReconSchemaVersionTableManager versionTableManager = - injector.getInstance(ReconSchemaVersionTableManager.class); - DataSource dataSource = injector.getInstance(DataSource.class); - - ReconLayoutVersionManager layoutVersionManager = - new ReconLayoutVersionManager(versionTableManager, reconContext, dataSource); - // Run the upgrade framework to finalize layout features if needed - layoutVersionManager.finalizeLayoutFeatures(); - - LOG.info("Recon schema versioning completed."); - // Register ReconTaskStatusMetrics after schema upgrade completes // This ensures the RECON_TASK_STATUS table has all required columns if (reconTaskStatusMetrics != null) { @@ -214,6 +203,31 @@ public Void call() throws Exception { return null; } + private void finalizeUpgrade() { + LOG.info("Finalizing Recon versions."); + ReconContext reconContext = injector.getInstance(ReconContext.class); + ReconVersionManager reconVersionManager = injector.getInstance(ReconVersionManager.class); + try { + reconVersionManager.finalizeUpgrade(); + } catch (UpgradeException e) { + LOG.error("Failed to finalize Recon versions.", e); + reconContext.updateErrors(ReconContext.ErrorCode.UPGRADE_FAILURE); + reconContext.updateHealthStatus(new AtomicBoolean(false)); + throw new RuntimeException("Recon failed to finalize schema versions. Startup halted.", e); + } + + try { + reconStorageContainerManager.finalizeScmVersionUpgrade(); + } catch (UpgradeException e) { + LOG.error("Failed to finalize Recon SCM apparent version.", e); + reconContext.updateErrors(ReconContext.ErrorCode.UPGRADE_FAILURE); + reconContext.updateHealthStatus(new AtomicBoolean(false)); + throw new RuntimeException("Recon failed to finalize SCM version. Startup halted.", e); + } + + LOG.info("Recon upgrade finalization completed."); + } + private void updateAndLogReconHealthStatus() { ReconContext reconContext = injector.getInstance(ReconContext.class); assert reconContext != null; diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconStorageContainerManagerFacade.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconStorageContainerManagerFacade.java index 466bc50878ab..686c64cc1c4c 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconStorageContainerManagerFacade.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconStorageContainerManagerFacade.java @@ -45,6 +45,7 @@ import java.net.InetSocketAddress; import java.time.Clock; import java.time.ZoneId; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -97,7 +98,6 @@ import org.apache.hadoop.hdds.scm.server.SCMDatanodeHeartbeatDispatcher.ContainerReport; import org.apache.hadoop.hdds.scm.server.SCMDatanodeHeartbeatDispatcher.ContainerReportFromDatanode; import org.apache.hadoop.hdds.scm.server.SCMDatanodeHeartbeatDispatcher.IncrementalContainerReportFromDatanode; -import org.apache.hadoop.hdds.scm.server.SCMStorageConfig; import org.apache.hadoop.hdds.scm.server.upgrade.ScmVersionManager; import org.apache.hadoop.hdds.server.events.EventQueue; import org.apache.hadoop.hdds.server.events.FixedThreadPoolWithAffinityExecutor; @@ -120,6 +120,7 @@ import org.apache.hadoop.ozone.recon.tasks.ContainerSizeCountTask; import org.apache.hadoop.ozone.recon.tasks.ReconTaskConfig; import org.apache.hadoop.ozone.recon.tasks.updater.ReconTaskStatusUpdaterManager; +import org.apache.hadoop.ozone.upgrade.UpgradeException; import org.apache.ozone.recon.schema.UtilizationSchemaDefinition; import org.apache.ozone.recon.schema.generated.tables.daos.ContainerCountBySizeDao; import org.apache.ratis.util.ExitUtils; @@ -145,7 +146,8 @@ public class ReconStorageContainerManagerFacade // This will hold the recon related information like health status and errors in initialization of modules if any, // which can later be used for alerts integration or displaying some meaningful info to user on Recon UI. private final ReconContext reconContext; - private final SCMStorageConfig scmStorageConfig; + private final ReconStorageConfig scmStorageConfig; + private final ScmVersionManager scmVersionManager; private final SCMNodeDetails reconNodeDetails; private final SCMHAManager scmhaManager; private final SequenceIdGenerator sequenceIdGen; @@ -180,6 +182,7 @@ public ReconStorageContainerManagerFacade(OzoneConfiguration conf, ReconUtils reconUtils, ReconSafeModeManager safeModeManager, ReconContext reconContext, + ReconStorageConfig reconStorageConfig, DataSource dataSource, ReconTaskStatusUpdaterManager taskStatusUpdaterManager, ContainerHealthSchemaManager containerHealthSchemaManager) @@ -211,12 +214,13 @@ public ReconStorageContainerManagerFacade(OzoneConfiguration conf, conf.setLong(HDDS_SCM_CLIENT_FAILOVER_MAX_RETRY, scmClientFailOverMaxRetryCount); - this.scmStorageConfig = new ReconStorageConfig(conf, reconUtils); + this.scmStorageConfig = reconStorageConfig; NetworkTopology clusterMap = new NetworkTopologyImpl(conf); this.dbStore = DBStoreBuilder.createDBStore(ozoneConfiguration, ReconSCMDBDefinition.get()); - // TODO HDDS-15374 Fully switch recon to the new versioning framework. - ScmVersionManager versionManager = new ScmVersionManager(scmStorageConfig, this); + // Use a version manager with no upgrade actions. The version will only be used to track Datanode versions, + // not run SCM specific reformatting on upgrade. + this.scmVersionManager = new ScmVersionManager(scmStorageConfig, this, HashMap::new); this.scmhaManager = SCMHAManagerStub.getInstance( true, new SCMDBTransactionBufferImpl()); this.sequenceIdGen = new SequenceIdGenerator( @@ -225,7 +229,7 @@ public ReconStorageContainerManagerFacade(OzoneConfiguration conf, this.nodeManager = new ReconNodeManager(conf, scmStorageConfig, eventQueue, clusterMap, ReconSCMDBDefinition.NODES.getTable(dbStore), - versionManager, reconContext); + scmVersionManager, reconContext); SCMContainerPlacementMetrics placementMetrics = SCMContainerPlacementMetrics.create(); PlacementPolicy containerPlacementPolicy = ContainerPlacementPolicyFactory.getPolicy(conf, nodeManager, clusterMap, true, placementMetrics); @@ -717,4 +721,8 @@ public ReconContext getReconContext() { public DataSource getDataSource() { return dataSource; } + + public void finalizeScmVersionUpgrade() throws UpgradeException { + scmVersionManager.finalizeUpgrade(); + } } diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/InitialConstraintUpgradeAction.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/InitialConstraintUpgradeAction.java index ea8af99d96e6..6f632de180ba 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/InitialConstraintUpgradeAction.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/InitialConstraintUpgradeAction.java @@ -17,11 +17,10 @@ package org.apache.hadoop.ozone.recon.upgrade; -import static org.apache.hadoop.ozone.recon.upgrade.ReconLayoutFeature.INITIAL_VERSION; +import static org.apache.hadoop.ozone.recon.upgrade.ReconVersion.INITIAL_VERSION; import static org.apache.ozone.recon.schema.ContainerSchemaDefinition.UNHEALTHY_CONTAINERS_TABLE_NAME; import static org.apache.ozone.recon.schema.SqlDbUtils.TABLE_EXISTS_CHECK; import static org.jooq.impl.DSL.field; -import static org.jooq.impl.DSL.name; import com.google.common.annotations.VisibleForTesting; import java.sql.Connection; @@ -82,7 +81,7 @@ private void addUpdatedConstraint() { dslContext.alterTable(ContainerSchemaDefinition.UNHEALTHY_CONTAINERS_TABLE_NAME) .add(DSL.constraint(ContainerSchemaDefinition.UNHEALTHY_CONTAINERS_TABLE_NAME + "ck1") - .check(field(name("container_state")) + .check(field(DSL.name("container_state")) .in(enumStates))) .execute(); diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/NSSummaryAggregatedTotalsUpgrade.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/NSSummaryAggregatedTotalsUpgrade.java index d4ee78c1a6a1..f153c4a35acc 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/NSSummaryAggregatedTotalsUpgrade.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/NSSummaryAggregatedTotalsUpgrade.java @@ -33,7 +33,7 @@ * Recon startup is not blocked. During rebuild, APIs that depend on * the tree may return initializing responses as designed. */ -@UpgradeActionRecon(feature = ReconLayoutFeature.NSSUMMARY_AGGREGATED_TOTALS) +@UpgradeActionRecon(feature = ReconVersion.NSSUMMARY_AGGREGATED_TOTALS) public class NSSummaryAggregatedTotalsUpgrade implements ReconUpgradeAction { private static final Logger LOG = LoggerFactory.getLogger(NSSummaryAggregatedTotalsUpgrade.class); diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconLayoutFeature.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconLayoutFeature.java deleted file mode 100644 index a1e8abf8d0c0..000000000000 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconLayoutFeature.java +++ /dev/null @@ -1,119 +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.recon.upgrade; - -import java.util.Arrays; -import java.util.Optional; -import java.util.Set; -import org.reflections.Reflections; - -/** - * Enum representing Recon layout features with their version, description, - * and associated upgrade action to be executed during an upgrade. - */ -public enum ReconLayoutFeature { - // Represents the starting point for Recon's layout versioning system. - INITIAL_VERSION(0, "Recon Layout Versioning Introduction"), - TASK_STATUS_STATISTICS(1, "Recon Task Status Statistics Tracking Introduced"), - UNHEALTHY_CONTAINER_REPLICA_MISMATCH(2, "Adding replica mismatch state to the unhealthy container table"), - - // HDDS-13432: Materialize NSSummary totals and rebuild tree on upgrade - NSSUMMARY_AGGREGATED_TOTALS(3, "Aggregated totals for NSSummary and auto-rebuild on upgrade"), - REPLICATED_SIZE_OF_FILES(4, "Adds replicatedSizeOfFiles to NSSummary"), - UNHEALTHY_CONTAINERS_STATE_CONTAINER_ID_INDEX(5, - "Adds idx_state_container_id index on UNHEALTHY_CONTAINERS for upgrades"); - - private final int version; - private final String description; - private ReconUpgradeAction action; - - ReconLayoutFeature(final int version, String description) { - this.version = version; - this.description = description; - } - - public int getVersion() { - return version; - } - - public String getDescription() { - return description; - } - - /** - * Retrieves the upgrade action for this feature. - * - * @return An {@link Optional} containing the upgrade action if present. - */ - public Optional getAction() { - return Optional.ofNullable(action); - } - - /** - * Associates a given upgrade action with this feature. Only the first upgrade action registered will be used. - * - * @param upgradeAction The upgrade action to associate with this feature. - */ - public void addAction(ReconUpgradeAction upgradeAction) { - // Required by SpotBugs since this setter exists in an enum. - if (this.action == null) { - this.action = upgradeAction; - } - } - - /** - * Scans the classpath for all classes annotated with {@link UpgradeActionRecon} - * and registers their upgrade actions for the corresponding feature. - * This method dynamically loads and registers all upgrade actions based on their - * annotations. - */ - public static void registerUpgradeActions() { - Reflections reflections = new Reflections("org.apache.hadoop.ozone.recon.upgrade"); - Set> actionClasses = reflections.getTypesAnnotatedWith(UpgradeActionRecon.class); - - for (Class actionClass : actionClasses) { - try { - ReconUpgradeAction action = (ReconUpgradeAction) actionClass.getDeclaredConstructor().newInstance(); - UpgradeActionRecon annotation = actionClass.getAnnotation(UpgradeActionRecon.class); - annotation.feature().addAction(action); - } catch (Exception e) { - throw new RuntimeException("Failed to register upgrade action: " + actionClass.getSimpleName(), e); - } - } - } - - /** - * Determines the Software Layout Version (SLV) based on the latest feature version. - * @return The Software Layout Version (SLV). - */ - public static int determineSLV() { - return Arrays.stream(ReconLayoutFeature.values()) - .mapToInt(ReconLayoutFeature::getVersion) - .max() - .orElse(0); // Default to 0 if no features are defined - } - - /** - * Returns the list of all layout feature values. - * - * @return An array of all {@link ReconLayoutFeature} values. - */ - public static ReconLayoutFeature[] getValues() { - return ReconLayoutFeature.values(); - } -} diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconLayoutVersionManager.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconLayoutVersionManager.java deleted file mode 100644 index cb12546771eb..000000000000 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconLayoutVersionManager.java +++ /dev/null @@ -1,164 +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.recon.upgrade; - -import java.sql.Connection; -import java.sql.SQLException; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; -import javax.sql.DataSource; -import org.apache.hadoop.ozone.recon.ReconContext; -import org.apache.hadoop.ozone.recon.ReconSchemaVersionTableManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * ReconLayoutVersionManager is responsible for managing the layout version of the Recon service. - * It determines the current Metadata Layout Version (MLV) and Software Layout Version (SLV) of the - * Recon service, and finalizes the layout features that need to be upgraded. - */ -public class ReconLayoutVersionManager { - - private static final Logger LOG = LoggerFactory.getLogger(ReconLayoutVersionManager.class); - - private final ReconSchemaVersionTableManager schemaVersionTableManager; - private final ReconContext reconContext; - private final DataSource dataSource; - - // Metadata Layout Version (MLV) of the Recon Metadata on disk - private int currentMLV; - - public ReconLayoutVersionManager(ReconSchemaVersionTableManager schemaVersionTableManager, - ReconContext reconContext, DataSource dataSource) - throws SQLException { - this.schemaVersionTableManager = schemaVersionTableManager; - this.currentMLV = determineMLV(); - this.reconContext = reconContext; - this.dataSource = dataSource; - ReconLayoutFeature.registerUpgradeActions(); // Register actions via annotation - } - - /** - * Determines the current Metadata Layout Version (MLV) from the version table. - * @return The current Metadata Layout Version (MLV). - */ - private int determineMLV() throws SQLException { - return schemaVersionTableManager.getCurrentSchemaVersion(); - } - - /** - * Determines the Software Layout Version (SLV) based on the latest feature version. - * @return The Software Layout Version (SLV). - */ - private int determineSLV() { - return Arrays.stream(ReconLayoutFeature.values()) - .mapToInt(ReconLayoutFeature::getVersion) - .max() - .orElse(0); - } - - /** - * Finalizes the layout features that need to be upgraded, by executing the upgrade action for each - * feature that is registered for finalization. - */ - public void finalizeLayoutFeatures() { - // Get features that need finalization, sorted by version - List featuresToFinalize = getRegisteredFeatures(); - LOG.debug("Starting finalization of {} features.", featuresToFinalize.size()); - - try (Connection connection = dataSource.getConnection()) { - connection.setAutoCommit(false); // Turn off auto-commit for transactional control - - for (ReconLayoutFeature feature : featuresToFinalize) { - LOG.debug("Processing feature version: {}", feature.getVersion()); - try { - // Fetch the action for the feature - Optional action = feature.getAction(); - if (action.isPresent()) { - LOG.debug("Finalize action found for feature version: {}", feature.getVersion()); - // Update the schema version in the database - updateSchemaVersion(feature.getVersion(), connection); - - // Execute the upgrade action - action.get().execute(dataSource); - - // Commit the transaction only if both operations succeed - connection.commit(); - LOG.info("Feature versioned {} finalized successfully.", feature.getVersion()); - } else { - LOG.info("No finalize action found for feature version: {}", feature.getVersion()); - } - } catch (Exception e) { - // Rollback any pending changes for the current feature due to failure - connection.rollback(); - currentMLV = determineMLV(); // Rollback the MLV to the original value - LOG.error("Failed to finalize feature {}. Rolling back changes.", feature.getVersion(), e); - throw e; - } - } - } catch (Exception e) { - // Log the error to both logs and ReconContext - LOG.error("Failed to finalize layout features: {}", e.getMessage()); - reconContext.updateErrors(ReconContext.ErrorCode.UPGRADE_FAILURE); - reconContext.updateHealthStatus(new AtomicBoolean(false)); - throw new RuntimeException("Recon failed to finalize layout features. Startup halted.", e); - } - } - - /** - * Returns a list of ReconLayoutFeature objects that are registered for finalization. - */ - protected List getRegisteredFeatures() { - List allFeatures = - Arrays.asList(ReconLayoutFeature.values()); - - LOG.info("Current MLV: {}. SLV: {}. Checking features for registration...", currentMLV, determineSLV()); - - List registeredFeatures = allFeatures.stream() - .filter(feature -> feature.getVersion() > currentMLV) - .sorted((a, b) -> Integer.compare(a.getVersion(), b.getVersion())) // Sort by version in ascending order - .collect(Collectors.toList()); - - return registeredFeatures; - } - - /** - * Updates the Metadata Layout Version (MLV) in the database after finalizing a feature. - * This method uses the provided connection to ensure transactional consistency. - * - * @param newVersion The new Metadata Layout Version (MLV) to set. - * @param connection The database connection to use for the update operation. - */ - private void updateSchemaVersion(int newVersion, Connection connection) { - schemaVersionTableManager.updateSchemaVersion(newVersion, connection); - this.currentMLV = newVersion; - LOG.info("MLV updated to: " + newVersion); - } - - public int getCurrentMLV() { - return currentMLV; - } - - public int getCurrentSLV() { - return determineSLV(); - } - -} diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconTaskStatusTableUpgradeAction.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconTaskStatusTableUpgradeAction.java index 17d64abb9c57..49e86b0d55e5 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconTaskStatusTableUpgradeAction.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconTaskStatusTableUpgradeAction.java @@ -36,7 +36,7 @@ * last_task_run_status and current_task_run_status columns to * {@link ReconTaskSchemaDefinition} in case it is missing . */ -@UpgradeActionRecon(feature = ReconLayoutFeature.TASK_STATUS_STATISTICS) +@UpgradeActionRecon(feature = ReconVersion.TASK_STATUS_STATISTICS) public class ReconTaskStatusTableUpgradeAction implements ReconUpgradeAction { private static final Logger LOG = LoggerFactory.getLogger(ReconTaskStatusTableUpgradeAction.class); diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconUpgradeAction.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconUpgradeAction.java index 67aa0e9d5696..3994223447fc 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconUpgradeAction.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconUpgradeAction.java @@ -18,14 +18,10 @@ package org.apache.hadoop.ozone.recon.upgrade; import javax.sql.DataSource; +import org.apache.hadoop.ozone.upgrade.UpgradeAction; /** - * ReconUpgradeAction is an interface for executing upgrade actions in Recon. + * Recon upgrade action executed during finalization of a {@link ReconVersion}. */ -public interface ReconUpgradeAction { - - /** - * Execute the upgrade action during finalization. - */ - void execute(DataSource source) throws Exception; +public interface ReconUpgradeAction extends UpgradeAction { } diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconUpgradeActionProvider.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconUpgradeActionProvider.java new file mode 100644 index 000000000000..76fb392d3bbb --- /dev/null +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconUpgradeActionProvider.java @@ -0,0 +1,39 @@ +/* + * 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.recon.upgrade; + +import org.apache.hadoop.hdds.ComponentVersion; +import org.apache.hadoop.ozone.upgrade.AbstractUpgradeActionProvider; + +/** + * Loads {@link ReconUpgradeAction} implementations annotated with {@link UpgradeActionRecon}. + */ +public final class ReconUpgradeActionProvider extends AbstractUpgradeActionProvider { + + public static final String RECON_UPGRADE_CLASS_PACKAGE = "org.apache.hadoop.ozone.recon.upgrade"; + + public ReconUpgradeActionProvider() { + super(UpgradeActionRecon.class, ReconUpgradeAction.class, RECON_UPGRADE_CLASS_PACKAGE); + } + + @Override + protected ComponentVersion extractVersion(Class clazz) { + UpgradeActionRecon annotation = clazz.getAnnotation(UpgradeActionRecon.class); + return annotation.feature(); + } +} diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconVersion.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconVersion.java new file mode 100644 index 000000000000..0e79f2a2a862 --- /dev/null +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconVersion.java @@ -0,0 +1,100 @@ +/* + * 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.recon.upgrade; + +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; + +import java.util.Arrays; +import java.util.SortedMap; +import java.util.TreeMap; +import org.apache.hadoop.hdds.ComponentVersion; + +/** + * Upgrade version handling for Recon. Currently Recon is finalized on startup and does not support downgrade, + * so versioning is only used to execute reformatting actions on upgrade. + * + *

{@link #INITIAL_VERSION} is the starting state when no version is recorded; it has no upgrade + * action. Finalization advances one version at a time via {@link ComponentVersion#nextVersion()}. + */ +public enum ReconVersion implements ComponentVersion { + /** Starting point for Recon versioning; not a transition target for upgrade actions. */ + INITIAL_VERSION(0, "Recon Layout Versioning Introduction"), + TASK_STATUS_STATISTICS(1, "Recon Task Status Statistics Tracking Introduced"), + UNHEALTHY_CONTAINER_REPLICA_MISMATCH(2, "Adding replica mismatch state to the unhealthy container table"), + + // HDDS-13432: Materialize NSSummary totals and rebuild tree on upgrade + NSSUMMARY_AGGREGATED_TOTALS(3, "Aggregated totals for NSSummary and auto-rebuild on upgrade"), + REPLICATED_SIZE_OF_FILES(4, "Adds replicatedSizeOfFiles to NSSummary"), + UNHEALTHY_CONTAINERS_STATE_CONTAINER_ID_INDEX(5, + "Adds idx_state_container_id index on UNHEALTHY_CONTAINERS for upgrades"), + + UNKNOWN_VERSION(-1, "Used when a version cannot be deserialized to any version recognized by this" + + " component, which may indicate it came from a component in a newer version"); + + private static final SortedMap BY_VALUE = + Arrays.stream(values()) + .collect(toMap(ReconVersion::serialize, identity(), (v1, v2) -> v1, TreeMap::new)); + + public static final ReconVersion SOFTWARE_VERSION = BY_VALUE.get(BY_VALUE.lastKey()); + + private final int version; + private final String description; + + ReconVersion(final int version, String description) { + this.version = version; + this.description = description; + } + + @Override + public int serialize() { + return version; + } + + /** + * @param value The serialized version to convert. + * @return The version corresponding to this serialized value, or {@link #UNKNOWN_VERSION} if no matching version is + * found. + */ + public static ReconVersion deserialize(int value) { + return BY_VALUE.getOrDefault(value, UNKNOWN_VERSION); + } + + @Override + public String description() { + return description; + } + + /** + * @return The next version immediately following this one and excluding {@link #UNKNOWN_VERSION}, + * or null if there is no such version. + */ + @Override + public ReconVersion nextVersion() { + int nextOrdinal = ordinal() + 1; + if (nextOrdinal >= values().length - 1) { + return null; + } + return values()[nextOrdinal]; + } + + @Override + public String toString() { + return name() + " (" + serialize() + ")"; + } +} diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconVersionManager.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconVersionManager.java new file mode 100644 index 000000000000..eeffc03e443b --- /dev/null +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconVersionManager.java @@ -0,0 +1,151 @@ +/* + * 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.recon.upgrade; + +import static org.apache.ozone.recon.schema.SchemaVersionTableDefinition.SCHEMA_VERSION_TABLE_NAME; +import static org.jooq.impl.DSL.name; + +import com.google.common.annotations.VisibleForTesting; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import java.io.IOException; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Map; +import javax.sql.DataSource; +import org.apache.hadoop.hdds.ComponentVersion; +import org.apache.hadoop.ozone.upgrade.ComponentUpgradeActionProvider; +import org.apache.hadoop.ozone.upgrade.ComponentVersionManager; +import org.apache.hadoop.ozone.upgrade.UpgradeException; +import org.jooq.DSLContext; +import org.jooq.InsertSetMoreStep; +import org.jooq.UpdateSetMoreStep; +import org.jooq.impl.DSL; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Recon-specific version manager for SQL schema upgrades. + */ +@Singleton +public class ReconVersionManager extends ComponentVersionManager { + + private static final Logger LOG = LoggerFactory.getLogger(ReconVersionManager.class); + + private final DataSource dataSource; + private final Map upgradeActions; + + @Inject + public ReconVersionManager(DataSource dataSource) throws SQLException { + this(dataSource, new ReconUpgradeActionProvider()); + } + + @VisibleForTesting + ReconVersionManager(DataSource dataSource, + ComponentUpgradeActionProvider upgradeActionProvider) throws SQLException { + super(loadApparentVersion(dataSource), ReconVersion.SOFTWARE_VERSION); + this.dataSource = dataSource; + this.upgradeActions = upgradeActionProvider.load(); + } + + private static ComponentVersion loadApparentVersion(DataSource dataSource) throws SQLException { + int persisted = readPersistedApparentVersion(dataSource); + ReconVersion apparent = ReconVersion.deserialize(persisted); + if (apparent == ReconVersion.UNKNOWN_VERSION) { + throw new SQLException("Initialization failed. Database contains unknown apparent version " + + persisted + " for software version " + ReconVersion.SOFTWARE_VERSION + + ". Make sure this component was not downgraded after finalization"); + } + return apparent; + } + + /** + * Returns the persisted apparent version from {@code RECON_SCHEMA_VERSION}. + * If the table has no row, returns {@link ReconVersion#INITIAL_VERSION}. + */ + private static int readPersistedApparentVersion(DataSource dataSource) throws SQLException { + try (Connection conn = dataSource.getConnection()) { + DSLContext dsl = DSL.using(conn); + return dsl.select(DSL.field(name("version_number"))) + .from(DSL.table(SCHEMA_VERSION_TABLE_NAME)) + .fetchOptional() + .map(record -> record.get( + DSL.field(name("version_number"), Integer.class))) + .orElse(ReconVersion.INITIAL_VERSION.serialize()); + } catch (Exception e) { + LOG.error("Failed to fetch the persisted apparent version.", e); + throw new SQLException("Unable to read apparent version from the table.", e); + } + } + + @VisibleForTesting + Map getUpgradeActionsForTesting() { + return upgradeActions; + } + + @Override + protected void persistApparentVersion(ComponentVersion newVersion) throws IOException { + int serializedVersion = newVersion.serialize(); + try (Connection conn = dataSource.getConnection()) { + DSLContext dsl = DSL.using(conn); + boolean recordExists = dsl.fetchExists(dsl.selectOne() + .from(DSL.table(SCHEMA_VERSION_TABLE_NAME))); + + if (recordExists) { + try (UpdateSetMoreStep update = dsl.update(DSL.table(SCHEMA_VERSION_TABLE_NAME)) + .set(DSL.field(name("version_number")), serializedVersion) + .set(DSL.field(name("applied_on")), DSL.currentTimestamp())) { + update.execute(); + } + LOG.info("Updated apparent version to '{}'.", serializedVersion); + } else { + try (InsertSetMoreStep insert = dsl.insertInto(DSL.table(SCHEMA_VERSION_TABLE_NAME)) + .set(DSL.field(name("version_number")), serializedVersion) + .set(DSL.field(name("applied_on")), DSL.currentTimestamp())) { + insert.execute(); + } + LOG.info("Inserted new apparent version '{}'.", serializedVersion); + } + } catch (SQLException e) { + throw new IOException("Failed to persist apparent version " + newVersion, e); + } + } + + @Override + public int getPersistedApparentVersion() { + try { + return readPersistedApparentVersion(dataSource); + } catch (SQLException e) { + throw new IllegalStateException("Failed to read persisted apparent version", e); + } + } + + @Override + protected void runUpgradeAction(ComponentVersion version) throws UpgradeException { + ReconUpgradeAction action = upgradeActions.get(version); + if (action == null) { + return; + } + try { + action.execute(dataSource); + } catch (Exception e) { + logAndThrow(e, "Recon upgrade action for version " + version + " failed.", + UpgradeException.ResultCodes.FINALIZE_UPGRADE_ACTION_FAILED); + } + } +} diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReplicatedSizeOfFilesUpgradeAction.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReplicatedSizeOfFilesUpgradeAction.java index 9d5a37cf7dba..b12e2e93eea1 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReplicatedSizeOfFilesUpgradeAction.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReplicatedSizeOfFilesUpgradeAction.java @@ -30,7 +30,7 @@ * The action triggers a full rebuild of the NSSummary ensuring that the new field: replicatedSizeOfFiles is correctly * populated for all objects. */ -@UpgradeActionRecon(feature = ReconLayoutFeature.REPLICATED_SIZE_OF_FILES) +@UpgradeActionRecon(feature = ReconVersion.REPLICATED_SIZE_OF_FILES) public class ReplicatedSizeOfFilesUpgradeAction implements ReconUpgradeAction { private static final Logger LOG = LoggerFactory.getLogger(ReplicatedSizeOfFilesUpgradeAction.class); diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/UnhealthyContainerReplicaMismatchAction.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/UnhealthyContainerReplicaMismatchAction.java index ebf8556f5c49..a5b313192cc1 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/UnhealthyContainerReplicaMismatchAction.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/UnhealthyContainerReplicaMismatchAction.java @@ -17,11 +17,10 @@ package org.apache.hadoop.ozone.recon.upgrade; -import static org.apache.hadoop.ozone.recon.upgrade.ReconLayoutFeature.UNHEALTHY_CONTAINER_REPLICA_MISMATCH; +import static org.apache.hadoop.ozone.recon.upgrade.ReconVersion.UNHEALTHY_CONTAINER_REPLICA_MISMATCH; import static org.apache.ozone.recon.schema.ContainerSchemaDefinition.UNHEALTHY_CONTAINERS_TABLE_NAME; import static org.apache.ozone.recon.schema.SqlDbUtils.TABLE_EXISTS_CHECK; import static org.jooq.impl.DSL.field; -import static org.jooq.impl.DSL.name; import java.sql.Connection; import java.sql.SQLException; @@ -80,7 +79,7 @@ private void addUpdatedConstraint() { dslContext.alterTable(ContainerSchemaDefinition.UNHEALTHY_CONTAINERS_TABLE_NAME) .add(DSL.constraint(ContainerSchemaDefinition.UNHEALTHY_CONTAINERS_TABLE_NAME + "ck1") - .check(field(name("container_state")) + .check(field(DSL.name("container_state")) .in(enumStates))) .execute(); diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/UnhealthyContainersStateContainerIdIndexUpgradeAction.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/UnhealthyContainersStateContainerIdIndexUpgradeAction.java index 6bd91b78a7e6..ebfe459a6fc7 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/UnhealthyContainersStateContainerIdIndexUpgradeAction.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/UnhealthyContainersStateContainerIdIndexUpgradeAction.java @@ -19,7 +19,6 @@ import static org.apache.ozone.recon.schema.ContainerSchemaDefinition.UNHEALTHY_CONTAINERS_TABLE_NAME; import static org.apache.ozone.recon.schema.SqlDbUtils.TABLE_EXISTS_CHECK; -import static org.jooq.impl.DSL.name; import java.sql.Connection; import java.sql.DatabaseMetaData; @@ -34,7 +33,7 @@ /** * Upgrade action to ensure idx_state_container_id exists on UNHEALTHY_CONTAINERS. */ -@UpgradeActionRecon(feature = ReconLayoutFeature.UNHEALTHY_CONTAINERS_STATE_CONTAINER_ID_INDEX) +@UpgradeActionRecon(feature = ReconVersion.UNHEALTHY_CONTAINERS_STATE_CONTAINER_ID_INDEX) public class UnhealthyContainersStateContainerIdIndexUpgradeAction implements ReconUpgradeAction { @@ -60,8 +59,8 @@ public void execute(DataSource source) throws Exception { UNHEALTHY_CONTAINERS_TABLE_NAME); dslContext.createIndex(INDEX_NAME) .on(DSL.table(UNHEALTHY_CONTAINERS_TABLE_NAME), - DSL.field(name("container_state")), - DSL.field(name("container_id"))) + DSL.field(DSL.name("container_state")), + DSL.field(DSL.name("container_id"))) .execute(); } catch (SQLException e) { throw new SQLException("Failed to create " + INDEX_NAME diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/UpgradeActionRecon.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/UpgradeActionRecon.java index d4b59a097997..010af79851fc 100644 --- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/UpgradeActionRecon.java +++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/UpgradeActionRecon.java @@ -53,7 +53,7 @@ public @interface UpgradeActionRecon { /** - * Defines the layout feature this upgrade action is associated with. + * Defines the component version this upgrade action is associated with. */ - ReconLayoutFeature feature(); + ReconVersion feature(); } diff --git a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/persistence/TestSchemaVersionTableDefinition.java b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/persistence/TestSchemaVersionTableDefinition.java index 3c01312c43bf..5db305044ccb 100644 --- a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/persistence/TestSchemaVersionTableDefinition.java +++ b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/persistence/TestSchemaVersionTableDefinition.java @@ -24,7 +24,7 @@ import static org.apache.ozone.recon.schema.StatsSchemaDefinition.GLOBAL_STATS_TABLE_NAME; import static org.jooq.impl.DSL.name; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.mock; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.sql.Connection; import java.sql.DatabaseMetaData; @@ -34,12 +34,10 @@ import java.sql.Types; import java.util.ArrayList; import java.util.List; -import javax.sql.DataSource; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; -import org.apache.hadoop.ozone.recon.ReconContext; -import org.apache.hadoop.ozone.recon.ReconSchemaVersionTableManager; -import org.apache.hadoop.ozone.recon.upgrade.ReconLayoutVersionManager; +import org.apache.hadoop.ozone.recon.upgrade.ReconVersion; +import org.apache.hadoop.ozone.recon.upgrade.ReconVersionManager; import org.apache.ozone.recon.schema.SchemaVersionTableDefinition; import org.jooq.DSLContext; import org.jooq.Record1; @@ -141,7 +139,8 @@ public void testSchemaVersionCRUDOperations() throws SQLException { * * Expected Outcome: * - The schema version table is created during initialization. - * - The MLV is set to the latest SLV (Software Layout Version), indicating the schema is up-to-date. + * - The persisted apparent version in the table is set to the current software version, indicating the schema is + * up-to-date. * - No upgrade actions are triggered as all tables are already at the latest version. */ @Test @@ -153,22 +152,17 @@ public void testFreshInstallScenario() throws Exception { // Initialize the schema SchemaVersionTableDefinition schemaVersionTable = new SchemaVersionTableDefinition(getDataSource()); - schemaVersionTable.setLatestSLV(3); // Assuming the latest SLV = 3 + schemaVersionTable.setSoftwareVersion(3); schemaVersionTable.initializeSchema(); // Verify that the SchemaVersionTable is created boolean tableExists = TABLE_EXISTS_CHECK.test(connection, SCHEMA_VERSION_TABLE_NAME); - assertEquals(true, tableExists, "The Schema Version Table should be created."); + assertTrue(tableExists, "The Schema Version Table should be created."); - // Initialize ReconSchemaVersionTableManager and ReconLayoutVersionManager - ReconSchemaVersionTableManager schemaVersionTableManager = new ReconSchemaVersionTableManager(getDataSource()); - DataSource mockDataSource = mock(DataSource.class); - ReconLayoutVersionManager layoutVersionManager = - new ReconLayoutVersionManager(schemaVersionTableManager, mock(ReconContext.class), mockDataSource); - - // Fetch and verify the current MLV - int mlv = layoutVersionManager.getCurrentMLV(); - assertEquals(3, mlv, "For a fresh install, MLV should be set to the latest SLV value."); + try (ReconVersionManager versionManager = new ReconVersionManager(getDataSource())) { + assertEquals(3, versionManager.getPersistedApparentVersion(), + "For a fresh install, apparent version should equal software version."); + } } /** @@ -178,7 +172,7 @@ public void testFreshInstallScenario() throws Exception { * * Expected Outcome: * - The schema version table is created during initialization. - * - The MLV is set to -1, indicating the starting point of the schema version framework. + * - The apparent version is INITIAL_VERSION (empty version table). * - Ensures only necessary upgrades are executed, avoiding redundant updates. */ @Test @@ -198,29 +192,24 @@ public void testPreUpgradedClusterScenario() throws Exception { // Verify SchemaVersionTable is created boolean tableExists = TABLE_EXISTS_CHECK.test(connection, SCHEMA_VERSION_TABLE_NAME); - assertEquals(true, tableExists, "The Schema Version Table should be created."); + assertTrue(tableExists, "The Schema Version Table should be created."); - // Initialize ReconSchemaVersionTableManager and ReconLayoutVersionManager - ReconSchemaVersionTableManager schemaVersionTableManager = new ReconSchemaVersionTableManager(getDataSource()); - DataSource mockDataSource = mock(DataSource.class); - ReconLayoutVersionManager layoutVersionManager = - new ReconLayoutVersionManager(schemaVersionTableManager, mock(ReconContext.class), mockDataSource); - - // Fetch and verify the current MLV - int mlv = layoutVersionManager.getCurrentMLV(); - assertEquals(-1, mlv, "For a pre-upgraded cluster, MLV should be set to -1."); + try (ReconVersionManager versionManager = new ReconVersionManager(getDataSource())) { + assertEquals(ReconVersion.INITIAL_VERSION.serialize(), versionManager.getPersistedApparentVersion(), + "For a pre-upgraded cluster, apparent version should be INITIAL_VERSION."); + } } /*** * Scenario: * - This simulates a cluster where the schema version table already exists, * indicating the schema version framework is in place. - * - The schema version table contains a previously finalized Metadata Layout Version (MLV). + * - The schema version table contains a previously finalized apparent version. * * Expected Outcome: - * - The MLV stored in the schema version table (2) is correctly read by the ReconLayoutVersionManager. - * - The MLV is retained and not overridden by the SLV value (3) during schema initialization. - * - This ensures no unnecessary upgrades are triggered and the existing MLV remains consistent. + * - The apparent version stored in the schema version table (2) is correctly read. + * - It is retained and not overridden by the software version (3) during schema initialization. + * - This ensures no unnecessary upgrades are triggered and the existing apparent version remains consistent. */ @Test public void testUpgradedClusterScenario() throws Exception { @@ -236,7 +225,7 @@ public void testUpgradedClusterScenario() throws Exception { createSchemaVersionTable(connection); } - // Insert a single existing MLV (e.g., version 2) into the Schema Version Table + // Insert apparent version 2 into the Schema Version Table DSLContext dslContext = DSL.using(connection); dslContext.insertInto(DSL.table(SCHEMA_VERSION_TABLE_NAME)) .columns(DSL.field(name("version_number")), @@ -246,22 +235,14 @@ public void testUpgradedClusterScenario() throws Exception { // Initialize the schema SchemaVersionTableDefinition schemaVersionTable = new SchemaVersionTableDefinition(getDataSource()); - schemaVersionTable.setLatestSLV(3); // Assuming the latest SLV = 3 + schemaVersionTable.setSoftwareVersion(3); schemaVersionTable.initializeSchema(); - // Initialize managers to interact with schema version framework - ReconSchemaVersionTableManager schemaVersionTableManager = new ReconSchemaVersionTableManager(getDataSource()); - DataSource mockDataSource = mock(DataSource.class); - ReconLayoutVersionManager layoutVersionManager = - new ReconLayoutVersionManager(schemaVersionTableManager, mock(ReconContext.class), mockDataSource); - - // Fetch and verify the current MLV stored in the database - int mlv = layoutVersionManager.getCurrentMLV(); - - // Assert that the MLV stored in the DB is retained and not overridden by the SLV value - // when running initializeSchema() before upgrade takes place - assertEquals(2, mlv, "For a cluster with an existing schema version framework, " + - "the MLV should match the value stored in the DB."); + try (ReconVersionManager versionManager = new ReconVersionManager(getDataSource())) { + assertEquals(2, versionManager.getPersistedApparentVersion(), + "For a cluster with an existing schema version framework, " + + "the apparent version should match the value stored in the DB."); + } } /** diff --git a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/upgrade/TestReconLayoutVersionManager.java b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/upgrade/TestReconLayoutVersionManager.java deleted file mode 100644 index 5f2d451dd066..000000000000 --- a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/upgrade/TestReconLayoutVersionManager.java +++ /dev/null @@ -1,356 +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.recon.upgrade; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.sql.Connection; -import java.sql.SQLException; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import javax.sql.DataSource; -import org.apache.hadoop.ozone.recon.ReconContext; -import org.apache.hadoop.ozone.recon.ReconSchemaVersionTableManager; -import org.apache.hadoop.ozone.recon.scm.ReconStorageContainerManagerFacade; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.InOrder; -import org.mockito.MockedStatic; - -/** - * Tests for ReconLayoutVersionManager. - */ -public class TestReconLayoutVersionManager { - - private ReconSchemaVersionTableManager schemaVersionTableManager; - private ReconLayoutVersionManager layoutVersionManager; - private MockedStatic mockedEnum; - private DataSource mockDataSource; - private Connection mockConnection; - - @BeforeEach - public void setUp() throws SQLException { - schemaVersionTableManager = mock(ReconSchemaVersionTableManager.class); - when(schemaVersionTableManager.getCurrentSchemaVersion()).thenReturn(0); - - // Mocking ReconLayoutFeature.values() to return custom enum instances - mockedEnum = mockStatic(ReconLayoutFeature.class); - - ReconLayoutFeature feature1 = mock(ReconLayoutFeature.class); - when(feature1.getVersion()).thenReturn(1); - ReconUpgradeAction action1 = mock(ReconUpgradeAction.class); - when(feature1.getAction()) - .thenReturn(Optional.of(action1)); - - ReconLayoutFeature feature2 = mock(ReconLayoutFeature.class); - when(feature2.getVersion()).thenReturn(2); - ReconUpgradeAction action2 = mock(ReconUpgradeAction.class); - when(feature2.getAction()) - .thenReturn(Optional.of(action2)); - - // Common mocks for all tests - ReconStorageContainerManagerFacade scmFacadeMock = mock(ReconStorageContainerManagerFacade.class); - mockDataSource = mock(DataSource.class); - mockConnection = mock(Connection.class); - - // Define the custom features to be returned - mockedEnum.when(ReconLayoutFeature::values).thenReturn(new ReconLayoutFeature[]{feature1, feature2}); - - layoutVersionManager = new ReconLayoutVersionManager(schemaVersionTableManager, mock(ReconContext.class), - mockDataSource); - - when(scmFacadeMock.getDataSource()).thenReturn(mockDataSource); - when(mockDataSource.getConnection()).thenReturn(mockConnection); - - doNothing().when(mockConnection).setAutoCommit(false); - doNothing().when(mockConnection).commit(); - doNothing().when(mockConnection).rollback(); - } - - @AfterEach - public void tearDown() { - // Close the static mock after each test to deregister it - mockedEnum.close(); - } - - /** - * Tests the initialization of layout version manager to ensure - * that the MLV (Metadata Layout Version) is set correctly to 0, - * and SLV (Software Layout Version) reflects the maximum available version. - */ - @Test - public void testInitializationWithMockedValues() { - assertEquals(0, layoutVersionManager.getCurrentMLV()); - assertEquals(2, layoutVersionManager.getCurrentSLV()); - } - - /** - * Tests the finalization of layout features and ensures that the updateSchemaVersion for - * the schemaVersionTable is triggered for each feature version. - */ - @Test - public void testFinalizeLayoutFeaturesWithMockedValues() throws SQLException { - // Execute the method under test - layoutVersionManager.finalizeLayoutFeatures(); - - // Verify that schema versions are updated for our custom features - verify(schemaVersionTableManager, times(1)) - .updateSchemaVersion(1, mockConnection); - verify(schemaVersionTableManager, times(1)) - .updateSchemaVersion(2, mockConnection); - } - - /** - * Tests the retrieval of registered features to ensure that the correct - * layout features are returned according to the mocked values. - */ - @Test - public void testGetRegisteredFeaturesWithMockedValues() { - // Fetch the registered features - List registeredFeatures = layoutVersionManager.getRegisteredFeatures(); - - // Verify that the registered features match the mocked ones - ReconLayoutFeature feature1 = ReconLayoutFeature.values()[0]; - ReconLayoutFeature feature2 = ReconLayoutFeature.values()[1]; - List expectedFeatures = Arrays.asList(feature1, feature2); - assertEquals(expectedFeatures, registeredFeatures); - } - - /** - * Tests the scenario where no layout features are present. Ensures that no schema - * version updates are attempted when there are no features to finalize. - */ - @Test - public void testNoLayoutFeatures() throws SQLException { - // Ensure no layout features are present - mockedEnum.when(ReconLayoutFeature::values).thenReturn(new ReconLayoutFeature[]{}); - - // Execute the method under test - layoutVersionManager.finalizeLayoutFeatures(); - - // Verify that no schema version updates were attempted - verify(schemaVersionTableManager, never()).updateSchemaVersion(anyInt(), any(Connection.class)); - } - - /** - * Tests the scenario where an upgrade action fails. Ensures that if an upgrade action - * throws an exception, the schema version is not updated. - */ - @Test - public void testUpgradeActionFailure() throws Exception { - // Reset existing mocks and set up new features for this specific test - mockedEnum.reset(); - - // Mock ReconLayoutFeature instances - ReconLayoutFeature feature1 = mock(ReconLayoutFeature.class); - when(feature1.getVersion()).thenReturn(1); - ReconUpgradeAction action1 = mock(ReconUpgradeAction.class); - - // Simulate an exception being thrown during the upgrade action execution - doThrow(new RuntimeException("Upgrade failed")).when(action1).execute(mockDataSource); - when(feature1.getAction()) - .thenReturn(Optional.of(action1)); - - // Mock the static values method to return the custom feature - mockedEnum.when(ReconLayoutFeature::values).thenReturn(new ReconLayoutFeature[]{feature1}); - - // Execute the layout feature finalization - try { - layoutVersionManager.finalizeLayoutFeatures(); - } catch (Exception e) { - // Exception is expected, so it's fine to catch and ignore it here - } - - // Verify that metadata layout version MLV was not updated as the transaction was rolled back - assertEquals(0, layoutVersionManager.getCurrentMLV()); - - // Verify that a rollback was triggered - verify(mockConnection, times(1)).rollback(); - } - - /** - * Tests the scenario where the schema version update fails. Ensures that if the schema - * version update fails, the transaction is rolled back and the metadata layout version - * is not updated. - */ - @Test - public void testUpdateSchemaFailure() throws Exception { - // Reset existing mocks and set up new features for this specific test - mockedEnum.reset(); - - // Mock ReconLayoutFeature instances - ReconLayoutFeature feature1 = mock(ReconLayoutFeature.class); - when(feature1.getVersion()).thenReturn(1); - ReconUpgradeAction action1 = mock(ReconUpgradeAction.class); - - // Simulate an exception being thrown during the schema version update - doThrow(new RuntimeException("Schema update failed")).when(schemaVersionTableManager). - updateSchemaVersion(1, mockConnection); - when(feature1.getAction()) - .thenReturn(Optional.of(action1)); - - // Mock the static values method to return the custom feature - mockedEnum.when(ReconLayoutFeature::values).thenReturn(new ReconLayoutFeature[]{feature1}); - - // Execute the layout feature finalization - try { - layoutVersionManager.finalizeLayoutFeatures(); - } catch (Exception e) { - // Exception is expected, so it's fine to catch and ignore it here - } - - // Verify that metadata layout version MLV was not updated as the transaction was rolled back - assertEquals(0, layoutVersionManager.getCurrentMLV()); - - // Verify that the upgrade action was not committed and a rollback was triggered - verify(mockConnection, times(1)).rollback(); - } - - /** - * Tests the order of execution for the upgrade actions to ensure that - * they are executed sequentially according to their version numbers. - */ - @Test - public void testUpgradeActionExecutionOrder() throws Exception { - // Reset the existing static mock for this specific test - mockedEnum.reset(); - - // Mock ReconLayoutFeature instances - ReconLayoutFeature feature1 = mock(ReconLayoutFeature.class); - when(feature1.getVersion()).thenReturn(1); - ReconUpgradeAction action1 = mock(ReconUpgradeAction.class); - when(feature1.getAction()) - .thenReturn(Optional.of(action1)); - - ReconLayoutFeature feature2 = mock(ReconLayoutFeature.class); - when(feature2.getVersion()).thenReturn(2); - ReconUpgradeAction action2 = mock(ReconUpgradeAction.class); - when(feature2.getAction()) - .thenReturn(Optional.of(action2)); - - ReconLayoutFeature feature3 = mock(ReconLayoutFeature.class); - when(feature3.getVersion()).thenReturn(3); - ReconUpgradeAction action3 = mock(ReconUpgradeAction.class); - when(feature3.getAction()) - .thenReturn(Optional.of(action3)); - - // Mock the static values method to return custom features in a jumbled order - mockedEnum.when(ReconLayoutFeature::values).thenReturn(new ReconLayoutFeature[]{feature2, feature3, feature1}); - - // Execute the layout feature finalization - layoutVersionManager.finalizeLayoutFeatures(); - - // Verify that the actions were executed in the correct order using InOrder - InOrder inOrder = inOrder(action1, action2, action3); - inOrder.verify(action1).execute(mockDataSource); // Should be executed first - inOrder.verify(action2).execute(mockDataSource); // Should be executed second - inOrder.verify(action3).execute(mockDataSource); // Should be executed third - } - - /** - * Tests the scenario where no upgrade actions are needed. Ensures that if the current - * schema version matches the maximum layout version, no upgrade actions are executed. - */ - @Test - public void testNoUpgradeActionsNeeded() throws SQLException { - // Mock the current schema version to the maximum layout version - when(schemaVersionTableManager.getCurrentSchemaVersion()).thenReturn(0); - - mockedEnum.when(ReconLayoutFeature::values).thenReturn(new ReconLayoutFeature[]{}); - - // Execute the method under test - layoutVersionManager.finalizeLayoutFeatures(); - - // Verify that no schema version updates were attempted - verify(schemaVersionTableManager, never()).updateSchemaVersion(anyInt(), eq(mockConnection)); - } - - /** - * Tests the scenario where the first two features are finalized, - * and then a third feature is introduced. Ensures that only the - * newly introduced feature is finalized while the previously - * finalized features are skipped. - */ - @Test - public void testFinalizingNewFeatureWithoutReFinalizingPreviousFeatures() throws Exception { - // Step 1: Mock the schema version manager - when(schemaVersionTableManager.getCurrentSchemaVersion()).thenReturn(0); - - // Mock the first two features - ReconLayoutFeature feature1 = mock(ReconLayoutFeature.class); - when(feature1.getVersion()).thenReturn(1); - ReconUpgradeAction action1 = mock(ReconUpgradeAction.class); - when(feature1.getAction()) - .thenReturn(Optional.of(action1)); - - ReconLayoutFeature feature2 = mock(ReconLayoutFeature.class); - when(feature2.getVersion()).thenReturn(2); - ReconUpgradeAction action2 = mock(ReconUpgradeAction.class); - when(feature2.getAction()) - .thenReturn(Optional.of(action2)); - - mockedEnum.when(ReconLayoutFeature::values).thenReturn(new ReconLayoutFeature[]{feature1, feature2}); - - // Finalize the first two features. - layoutVersionManager.finalizeLayoutFeatures(); - - // Verify that the schema versions for the first two features were updated - verify(schemaVersionTableManager, times(1)).updateSchemaVersion(1, mockConnection); - verify(schemaVersionTableManager, times(1)).updateSchemaVersion(2, mockConnection); - - // Step 2: Introduce a new feature (Feature 3) - ReconLayoutFeature feature3 = mock(ReconLayoutFeature.class); - when(feature3.getVersion()).thenReturn(3); - ReconUpgradeAction action3 = mock(ReconUpgradeAction.class); - when(feature3.getAction()) - .thenReturn(Optional.of(action3)); - - mockedEnum.when(ReconLayoutFeature::values).thenReturn(new ReconLayoutFeature[]{feature1, feature2, feature3}); - - // Update schema version to simulate that features 1 and 2 have already been finalized. - when(schemaVersionTableManager.getCurrentSchemaVersion()).thenReturn(2); - - // Finalize again, but only feature 3 should be finalized. - layoutVersionManager.finalizeLayoutFeatures(); - - // Verify that the schema version for feature 3 was updated - verify(schemaVersionTableManager, times(1)).updateSchemaVersion(3, mockConnection); - - // Verify that action1 and action2 were not executed again. - verify(action1, times(1)).execute(mockDataSource); - verify(action2, times(1)).execute(mockDataSource); - - // Verify that the upgrade action for feature 3 was executed. - verify(action3, times(1)).execute(mockDataSource); - } - -} diff --git a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/upgrade/TestReconVersion.java b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/upgrade/TestReconVersion.java new file mode 100644 index 000000000000..9bda982837d2 --- /dev/null +++ b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/upgrade/TestReconVersion.java @@ -0,0 +1,59 @@ +/* + * 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.recon.upgrade; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.hadoop.hdds.AbstractComponentVersionTest; +import org.apache.hadoop.hdds.ComponentVersion; +import org.junit.jupiter.api.Test; + +/** + * Invariants for {@link ReconVersion}. + */ +public class TestReconVersion extends AbstractComponentVersionTest { + + @Override + protected ComponentVersion[] getValues() { + return ReconVersion.values(); + } + + @Override + protected ComponentVersion getDefaultVersion() { + return ReconVersion.INITIAL_VERSION; + } + + @Override + protected ComponentVersion getUnknownVersion() { + return ReconVersion.UNKNOWN_VERSION; + } + + @Override + protected ComponentVersion deserialize(int value) { + return ReconVersion.deserialize(value); + } + + @Test + public void testKnownVersionNumbersAreContiguous() { + ReconVersion[] values = ReconVersion.values(); + int knownVersionCount = values.length - 1; + for (int i = 0; i < knownVersionCount - 1; i++) { + assertEquals(values[i].serialize() + 1, values[i + 1].serialize()); + } + } +} diff --git a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/upgrade/TestReconVersionManager.java b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/upgrade/TestReconVersionManager.java new file mode 100644 index 000000000000..24033063e31d --- /dev/null +++ b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/upgrade/TestReconVersionManager.java @@ -0,0 +1,237 @@ +/* + * 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.recon.upgrade; + +import static org.apache.ozone.recon.schema.SchemaVersionTableDefinition.SCHEMA_VERSION_TABLE_NAME; +import static org.jooq.impl.DSL.name; +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.Mockito.atLeastOnce; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Provider; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.sql.DataSource; +import org.apache.hadoop.hdds.ComponentVersion; +import org.apache.hadoop.ozone.recon.persistence.AbstractReconSqlDBTest; +import org.apache.hadoop.ozone.recon.persistence.DataSourceConfiguration; +import org.apache.hadoop.ozone.recon.persistence.JooqPersistenceModule; +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.jooq.Configuration; +import org.jooq.CreateTableColumnStep; +import org.jooq.DSLContext; +import org.jooq.InsertSetMoreStep; +import org.jooq.impl.DSL; +import org.jooq.impl.SQLDataType; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.provider.Arguments; + +/** + * Tests for {@link ReconVersionManager}. + */ +class TestReconVersionManager extends AbstractComponentVersionManagerTest { + + @TempDir + private Path tempDir; + + private static final List ALL_VERSIONS = + Arrays.stream(ReconVersion.values()) + .filter(v -> v != ReconVersion.UNKNOWN_VERSION) + .collect(Collectors.toList()); + + public static Stream preFinalizedVersionArgs() { + return ALL_VERSIONS.stream() + .limit(ALL_VERSIONS.size() - 1) + .map(Arguments::of); + } + + @Override + protected ComponentVersionManager createManager(int serializedApparentVersion) throws IOException { + return createManager(serializedApparentVersion, HashMap::new); + } + + private ReconVersionManager createManager(int serializedApparentVersion, + ComponentUpgradeActionProvider actions) throws IOException { + try { + Injector injector = createTestInjector("recon-version-"); + DataSource dataSource = injector.getInstance(DataSource.class); + initializeVersionTable(injector.getInstance(Configuration.class), serializedApparentVersion); + return new ReconVersionManager(dataSource, actions); + } catch (SQLException e) { + throw new IOException(e); + } + } + + private Injector createTestInjector(String directoryPrefix) throws IOException { + Path dbDir = Files.createTempDirectory(tempDir, directoryPrefix); + File configDir = Files.createDirectory(dbDir.resolve("Config")).toFile(); + Provider configProvider = + new AbstractReconSqlDBTest.DerbyDataSourceConfigurationProvider(configDir); + return Guice.createInjector( + new JooqPersistenceModule(configProvider), + new AbstractModule() { + @Override + protected void configure() { + bind(DataSourceConfiguration.class).toProvider(configProvider); + } + }); + } + + private static void initializeVersionTable(Configuration configuration, + int serializedApparentVersion) throws SQLException { + try (DSLContext dsl = DSL.using(configuration)) { + try (CreateTableColumnStep createTable = dsl.createTableIfNotExists(SCHEMA_VERSION_TABLE_NAME) + .column("version_number", SQLDataType.INTEGER.nullable(false)) + .column("applied_on", SQLDataType.TIMESTAMP)) { + createTable.execute(); + } + dsl.deleteFrom(DSL.table(SCHEMA_VERSION_TABLE_NAME)).execute(); + if (serializedApparentVersion != ReconVersion.INITIAL_VERSION.serialize()) { + try (InsertSetMoreStep insert = dsl.insertInto(DSL.table(SCHEMA_VERSION_TABLE_NAME)) + .set(DSL.field(name("version_number")), serializedApparentVersion) + .set(DSL.field(name("applied_on")), DSL.currentTimestamp())) { + insert.execute(); + } + } + } + } + + @Override + protected List allVersionsInOrder() { + return ALL_VERSIONS; + } + + @Override + protected ComponentVersion expectedSoftwareVersion() { + return ReconVersion.SOFTWARE_VERSION; + } + + @Override + @Test + public void testClasspathScanDiscoversUpgradeActions() throws Exception { + try (ReconVersionManager versionManager = createManager( + ReconVersion.INITIAL_VERSION.serialize(), new ReconUpgradeActionProvider())) { + assertTrue(versionManager.needsFinalization()); + ReconUpgradeAction taskStatusAction = versionManager.getUpgradeActionsForTesting() + .get(ReconVersion.TASK_STATUS_STATISTICS); + assertInstanceOf(ReconTaskStatusTableUpgradeAction.class, taskStatusAction); + } + + try (ReconVersionManager versionManager = createManager( + ReconVersion.SOFTWARE_VERSION.serialize(), new ReconUpgradeActionProvider())) { + assertFalse(versionManager.needsFinalization()); + ReconUpgradeAction taskStatusAction = versionManager.getUpgradeActionsForTesting() + .get(ReconVersion.TASK_STATUS_STATISTICS); + assertInstanceOf(ReconTaskStatusTableUpgradeAction.class, taskStatusAction); + } + } + + @Override + @Test + public void testFinalizeRunsSuppliedUpgradeAction() throws Exception { + ReconUpgradeAction mockReplicaMismatchAction = mock(ReconUpgradeAction.class); + ReconUpgradeAction mockContainerIdIndexAction = mock(ReconUpgradeAction.class); + + ComponentUpgradeActionProvider provider = () -> { + Map m = new HashMap<>(); + m.put(ReconVersion.UNHEALTHY_CONTAINER_REPLICA_MISMATCH, mockReplicaMismatchAction); + m.put(ReconVersion.UNHEALTHY_CONTAINERS_STATE_CONTAINER_ID_INDEX, mockContainerIdIndexAction); + return m; + }; + + try (ReconVersionManager versionManager = createManager( + ReconVersion.UNHEALTHY_CONTAINER_REPLICA_MISMATCH.serialize(), provider)) { + versionManager.finalizeUpgrade(); + assertEquals(ReconVersion.SOFTWARE_VERSION, versionManager.getApparentVersion()); + + // Apparent version is already UNHEALTHY_CONTAINER_REPLICA_MISMATCH; finalization runs actions for later + // versions only, not for UNHEALTHY_CONTAINER_REPLICA_MISMATCH itself. + verify(mockReplicaMismatchAction, never()).execute(any()); + verify(mockContainerIdIndexAction, atLeastOnce()).execute(any()); + assertEquals(ReconVersion.SOFTWARE_VERSION.serialize(), + versionManager.getPersistedApparentVersion()); + } + } + + @Override + @Test + public void testUpgradeActionFailureAbortsFinalize() throws Exception { + ComponentUpgradeActionProvider provider = () -> { + Map m = new HashMap<>(); + m.put(ReconVersion.TASK_STATUS_STATISTICS, ds -> { + throw new IOException("expected test failure"); + }); + return m; + }; + + try (ReconVersionManager versionManager = createManager( + ReconVersion.INITIAL_VERSION.serialize(), provider)) { + UpgradeException thrown = assertThrows(UpgradeException.class, versionManager::finalizeUpgrade); + assertEquals(UpgradeException.ResultCodes.FINALIZE_UPGRADE_ACTION_FAILED, thrown.getResult()); + assertEquals(ReconVersion.INITIAL_VERSION, versionManager.getApparentVersion()); + assertEquals(ReconVersion.INITIAL_VERSION.serialize(), + versionManager.getPersistedApparentVersion()); + } + } + + @Override + @Test + public void testPersistFailureRollsBack() throws Exception { + Injector injector = createTestInjector("recon-persist-fail-"); + DataSource dataSource = injector.getInstance(DataSource.class); + initializeVersionTable(injector.getInstance(Configuration.class), + ReconVersion.INITIAL_VERSION.serialize()); + ReconVersionManager versionManager = spy(new ReconVersionManager(dataSource, HashMap::new)); + doThrow(new IOException("persist failed")) + .when(versionManager).persistApparentVersion(ReconVersion.TASK_STATUS_STATISTICS); + + try (ReconVersionManager manager = versionManager) { + assertEquals(ReconVersion.INITIAL_VERSION, manager.getApparentVersion()); + UpgradeException thrown = assertThrows(UpgradeException.class, manager::finalizeUpgrade); + assertEquals(UpgradeException.ResultCodes.APPARENT_VERSION_UPDATE_FAILED, thrown.getResult()); + assertEquals(ReconVersion.INITIAL_VERSION, manager.getApparentVersion()); + assertEquals(ReconVersion.INITIAL_VERSION.serialize(), + manager.getPersistedApparentVersion()); + } + } +}