errose28 commented on code in PR #11800:
URL: https://github.com/apache/ozone/pull/11800#discussion_r4473912162


##########
hadoop-hdds/framework/src/main/java/org/apache/hadoop/ozone/upgrade/RatisBasedVersionManager.java:
##########
@@ -46,14 +46,32 @@ public void validateDBVersion(Table<String, String> 
finalizationStore) throws IO
     }
   }
 
-  public void finalizeFromSnapshotIfRequired(Table<String, String> 
finalizationStore) throws IOException {
+  public void finalizeFromDBIfRequired(Table<String, String> finalizationStore)

Review Comment:
   I took a lot more time workshop this method and went through a few 
iterations with claude. I'm reasonably confident about this implementation but 
please review it. Key points:
   - Every potential range the DB version can land in is now enumerated and 
given at least a comment indicating what is expected.
   - Ratis log replay happens async on startup, so we cannot guarantee that it 
has finished when this method runs. This means apparent version < DB version is 
valid. The new implementation treats this as an expected case instead of 
logging or blocking until log replay finishes.
   - The only unexpected case we truly can't handle and need to crash on is if 
the version in the DB is larger than our known software version.
     - For the other unexpected case where the DB version is between our 
apparent and software versions, the safest thing to do is partially finalize to 
that version.
       - Choosing either our apparent or software versions instead risks 
running mixed apparent versions in the quorum.
       - Throwing in this case would lead to an unrecoverable error if a bug 
ever allows this to happen.
   
   Test cases will need to be updated to test a DB version in every possible 
range relative to apparent and software versions.
   
   ```diff
   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 5aaa48aec1..125dc51474 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
   @@ -90,8 +90,22 @@ public boolean needsFinalization() {
      public abstract int getPersistedApparentVersion();
    
      public void finalizeUpgrade() throws UpgradeException {
   +    finalizeUpgrade(softwareVersion);
   +  }
   +
   +  /**
   +   * Finalizes each version after the current apparent version, up to and 
including {@code targetVersion}. Does nothing
   +   * if {@code targetVersion} is already finalized.
   +   *
   +   * @param targetVersion The last version to finalize. Must be supported 
by the software version.
   +   */
   +  public void finalizeUpgrade(ComponentVersion targetVersion) throws 
UpgradeException {
   +    if (!targetVersion.isSupportedBy(softwareVersion)) {
   +      throw new IllegalArgumentException("Cannot finalize to version " + 
targetVersion
   +          + " which is newer than software version " + softwareVersion + 
".");
   +    }
        ComponentVersion prevVersion = apparentVersion;
   -    for (ComponentVersion newVersion : getUnfinalizedVersions()) {
   +    for (ComponentVersion newVersion : 
getUnfinalizedVersions(targetVersion)) {
          validateForFinalization(newVersion);
          runUpgradeAction(newVersion);
          persistApparentVersion(newVersion, prevVersion);
   @@ -110,16 +124,18 @@ public void finalizeUpgrade() throws UpgradeException {
      public abstract ComponentVersion getVersionForClient();
    
      /**
   -   * @return An Iterable of all versions after the current apparent version 
which still need to be finalized. If this
   -   *    component is already finalized, the Iterable will be empty.
   +   * @return An Iterable of all versions after the current apparent 
version, up to and including
   +   *    {@code targetVersion}, which still need to be finalized. If {@code 
targetVersion} is already finalized, the
   +   *    Iterable will be empty.
       */
   -  private Iterable<ComponentVersion> getUnfinalizedVersions() {
   +  private Iterable<ComponentVersion> 
getUnfinalizedVersions(ComponentVersion targetVersion) {
        return () -> new Iterator<ComponentVersion>() {
          private ComponentVersion currentVersion = apparentVersion;
    
          @Override
          public boolean hasNext() {
   -        return currentVersion.nextVersion() != null;
   +        ComponentVersion nextVersion = currentVersion.nextVersion();
   +        return nextVersion != null && 
nextVersion.isSupportedBy(targetVersion);
          }
    
          @Override
   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
   index a1dd5baa56..573b371993 100644
   --- 
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
   @@ -46,33 +46,44 @@ public void validateDBVersion(Table<String, String> 
finalizationStore) throws IO
        }
      }
    
   -  public void finalizeFromDBIfRequired(Table<String, String> 
finalizationStore)
   -      throws IOException {
   -    ComponentVersion apparentVersionInNewDB = 
getApparentVersionInDB(finalizationStore);
   -    if (apparentVersionInNewDB == null) {
   -      LOG.info("Component has apparent version {} but no apparent version 
was found in the DB. That is expected if " +
   +  public void finalizeFromDBIfRequired(Table<String, String> 
finalizationStore) throws IOException {
   +    // This call is expected to throw and crash the process if the apparent 
version in the DB is larger than our
   +    // software version, which we cannot support.
   +    ComponentVersion apparentVersionInDB = 
getApparentVersionInDB(finalizationStore);
   +    if (apparentVersionInDB == null) {
   +      LOG.debug("Component has apparent version {} but no apparent version 
was found in the DB. That is expected if " +
              "this component has never been finalized to a newer version.", 
getApparentVersion());
   -    } else if (!isAllowed(apparentVersionInNewDB)) {
   -      // The DB is ahead of the VERSION file. This can happen if the leader 
finalized and this follower received the
   -      // update through a snapshot. It can also happen if the component was 
finalizing from a snapshot, failed
   -      // partway through, and restarted.
   -      if (apparentVersionInNewDB != getSoftwareVersion()) {
   -        LOG.warn("Received apparent version {} in DB which does not match 
the current apparent version {} or " +
   -                "software version {}. User may have incorrectly run 
finalization before all components were " +
   -                "upgraded. Finalizing this component to its software 
version.",
   -            apparentVersionInNewDB, getApparentVersion(), 
getSoftwareVersion());
   -      } else if (getApparentVersion().serialize() < 
apparentVersionInNewDB.serialize()) {
   -        LOG.info("Version file has lower apparent version ({}) than DB 
({}). Finalizing to the DB version.",
   -            getApparentVersion(), apparentVersionInNewDB);
   +    } else if (!apparentVersionInDB.equals(getApparentVersion())) {
   +      // We only need to take potential action if DB version != apparent 
version.
   +      if (isAllowed(apparentVersionInDB)) {
   +        // DB version < apparent version.
   +        LOG.debug("Received apparent version {} in DB which is less than 
the current apparent version {}. " +
   +            "Component likely previously crashed during finalization. Ratis 
log replay should correct this issue " +
   +            "after startup.",
   +            apparentVersionInDB, getApparentVersion());
   +      } else {
   +        // DB version > apparent version
   +        // We must finalize to this version.
   +        if (apparentVersionInDB.isSupportedBy(getSoftwareVersion()) &&
   +            !apparentVersionInDB.equals(getSoftwareVersion())) {
   +          // DB version < software version.
   +          // This should never happen.
   +          // When restarting after failed finalization, the DB should not 
have been updated until all versions up to
   +          // the software version were finalized.
   +          // On the snapshot installation path all components should be 
running the same software version when
   +          // finalization is run.
   +          LOG.warn("Received apparent version {} in DB which is between the 
current apparent version {} " +
   +              "and the software version {}. User may have incorrectly run 
finalization in a mixed version " +
   +              "cluster.", apparentVersionInDB, getApparentVersion(), 
getSoftwareVersion());
   +        } else {
   +          // DB version == software version
   +          // This is expected when we receive finalization from a Ratis 
snapshot.
   +          // The previous attempt to finalize from a snapshot may have 
failed, so we will resume it here.
   +          LOG.info("Version file has lower apparent version ({}) than DB 
({}). Finalizing to the DB version.",
   +              getApparentVersion(), apparentVersionInDB);
   +        }
   +        finalizeUpgrade(apparentVersionInDB);
          }
   -      finalizeUpgrade();
   -    } else if (!apparentVersionInNewDB.equals(getApparentVersion())) {
   -      // The DB is behind the VERSION file. This should not happen: 
finalization writes the VERSION file inside
   -      // applyTransaction, so the Ratis log entry was committed and should 
be replayed on restart, which would
   -      // update the DB. This is a defensive branch to catch any potential 
bugs.
   -      LOG.warn("DB has lower apparent version ({}) than VERSION file ({}). 
Updating DB to match VERSION file.",
   -          apparentVersionInNewDB, getApparentVersion());
   -      finalizationStore.put(APPARENT_VERSION_KEY, 
String.valueOf(getApparentVersion().serialize()));
        }
      }
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to