This is an automated email from the ASF dual-hosted git repository.
errose28 pushed a commit to branch HDDS-14496-zdu
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/HDDS-14496-zdu by this push:
new ab6bd667a38 HDDS-15715. Add helpful CLI flags to finalization CLIs
(#10678)
ab6bd667a38 is described below
commit ab6bd667a388c98c44509264ad6d6f4ee417ac80
Author: Zita Dombi <[email protected]>
AuthorDate: Mon Jul 27 17:14:42 2026 +0200
HDDS-15715. Add helpful CLI flags to finalization CLIs (#10678)
---
.../interface-client/src/main/proto/hdds.proto | 12 +-
.../apache/hadoop/hdds/scm/node/NodeManager.java | 75 +++++++-
.../hdds/scm/server/SCMClientProtocolServer.java | 12 +-
.../hadoop/hdds/scm/node/TestSCMNodeManager.java | 30 ++++
.../scm/server/TestSCMClientProtocolServer.java | 30 ++--
.../ozone/admin/upgrade/FinalizeSubCommand.java | 66 ++++++-
.../ozone/admin/upgrade/StatusSubCommand.java | 114 +++++++++++-
.../admin/upgrade/TestFinalizeSubCommand.java | 200 +++++++++++++++++++++
.../ozone/admin/upgrade/TestStatusSubCommand.java | 92 +++++++++-
.../hadoop/hdds/upgrade/HddsUpgradeTestUtils.java | 2 +-
.../apache/hadoop/ozone/om/OMUpgradeTestUtils.java | 22 ++-
.../src/main/proto/OmClientProtocol.proto | 5 +-
.../org/apache/hadoop/ozone/om/OzoneManager.java | 20 ++-
.../ozone/om/upgrade/OMUpgradeFinalizeService.java | 12 +-
.../om/upgrade/TestOMUpgradeFinalizeService.java | 10 +-
.../protocolPB/TestOzoneManagerRequestHandler.java | 2 +-
16 files changed, 647 insertions(+), 57 deletions(-)
diff --git a/hadoop-hdds/interface-client/src/main/proto/hdds.proto
b/hadoop-hdds/interface-client/src/main/proto/hdds.proto
index 53ad3db8822..fe19f300089 100644
--- a/hadoop-hdds/interface-client/src/main/proto/hdds.proto
+++ b/hadoop-hdds/interface-client/src/main/proto/hdds.proto
@@ -417,6 +417,7 @@ message BlockID {
optional uint64 blockCommitSequenceId = 2 [default = 0];
}
+// Deprecated, please use UpgradeStatus instead; kept for compatibility with
old clients.
message UpgradeFinalizationStatus {
enum Status {
ALREADY_FINALIZED = 1;
@@ -430,10 +431,13 @@ message UpgradeFinalizationStatus {
}
message UpgradeStatus {
- optional bool scmFinalized = 1;
- optional int32 numDatanodesFinalized = 2;
- optional int32 numDatanodesTotal = 3;
- optional bool shouldFinalize = 4;
+ optional bool hddsFinalized = 1;
+ optional bool scmFinalized = 2;
+ optional int32 numDatanodesFinalized = 3;
+ optional int32 numDatanodesTotal = 4;
+ optional uint32 scmApparentVersion = 5;
+ optional uint32 minDatanodeApparentVersion = 6;
+ optional uint32 maxDatanodeApparentVersion = 7;
}
/**
diff --git
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/node/NodeManager.java
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/node/NodeManager.java
index 96eafa0b2aa..a3afe1f2506 100644
---
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/node/NodeManager.java
+++
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/node/NodeManager.java
@@ -152,6 +152,8 @@ default int getAllNodeCount() {
default DatanodeFinalizationCounts getDatanodeFinalizationCounts() {
int finalizedNodes = 0;
int totalHealthyNodes = 0;
+ int minApparentVersion = Integer.MAX_VALUE;
+ int maxApparentVersion = 0;
for (DatanodeInfo dn : getAllNodes()) {
try {
@@ -173,6 +175,10 @@ default DatanodeFinalizationCounts
getDatanodeFinalizationCounts() {
ComponentVersion dnApparentVersion = dn.getLastKnownApparentVersion();
ComponentVersion dnSoftwareVersion = dn.getLastKnownSoftwareVersion();
+ int dnApparentVersionInt = dnApparentVersion.serialize();
+ minApparentVersion = Math.min(minApparentVersion,
dnApparentVersionInt);
+ maxApparentVersion = Math.max(maxApparentVersion,
dnApparentVersionInt);
+
if (!dnApparentVersion.equals(dnSoftwareVersion)) {
// Datanode has not yet finalized
LOG.debug("Datanode {} has not yet finalized: apparent version={},
software version={}",
@@ -187,7 +193,17 @@ default DatanodeFinalizationCounts
getDatanodeFinalizationCounts() {
}
}
- return new DatanodeFinalizationCounts(finalizedNodes, totalHealthyNodes);
+ if (minApparentVersion == Integer.MAX_VALUE) {
+ // No healthy datanode with version info was found
+ minApparentVersion = 0;
+ }
+
+ return DatanodeFinalizationCounts.newBuilder()
+ .setNumFinalizedDatanodes(finalizedNodes)
+ .setTotalHealthyDatanodes(totalHealthyNodes)
+ .setMinApparentVersion(minApparentVersion)
+ .setMaxApparentVersion(maxApparentVersion)
+ .build();
}
/**
@@ -498,11 +514,18 @@ default void removeNode(DatanodeDetails datanodeDetails)
throws NodeNotFoundExce
final class DatanodeFinalizationCounts {
private final int numFinalizedDatanodes;
private final int totalHealthyDatanodes;
+ private final int minApparentVersion;
+ private final int maxApparentVersion;
+
+ private DatanodeFinalizationCounts(Builder b) {
+ this.numFinalizedDatanodes = b.numFinalizedDatanodes;
+ this.totalHealthyDatanodes = b.totalHealthyDatanodes;
+ this.minApparentVersion = b.minApparentVersion;
+ this.maxApparentVersion = b.maxApparentVersion;
+ }
- public DatanodeFinalizationCounts(int numFinalizedDatanodes,
- int totalHealthyDatanodes) {
- this.numFinalizedDatanodes = numFinalizedDatanodes;
- this.totalHealthyDatanodes = totalHealthyDatanodes;
+ public static Builder newBuilder() {
+ return new Builder();
}
public int getNumFinalizedDatanodes() {
@@ -516,5 +539,47 @@ public int getTotalHealthyDatanodes() {
public boolean allNodesFinalized() {
return numFinalizedDatanodes == totalHealthyDatanodes;
}
+
+ public int getMinApparentVersion() {
+ return minApparentVersion;
+ }
+
+ public int getMaxApparentVersion() {
+ return maxApparentVersion;
+ }
+
+ /**
+ * Builder for {@link DatanodeFinalizationCounts}.
+ */
+ public static final class Builder {
+ private int numFinalizedDatanodes;
+ private int totalHealthyDatanodes;
+ private int minApparentVersion;
+ private int maxApparentVersion;
+
+ public Builder setNumFinalizedDatanodes(int value) {
+ this.numFinalizedDatanodes = value;
+ return this;
+ }
+
+ public Builder setTotalHealthyDatanodes(int value) {
+ this.totalHealthyDatanodes = value;
+ return this;
+ }
+
+ public Builder setMinApparentVersion(int value) {
+ this.minApparentVersion = value;
+ return this;
+ }
+
+ public Builder setMaxApparentVersion(int value) {
+ this.maxApparentVersion = value;
+ return this;
+ }
+
+ public DatanodeFinalizationCounts build() {
+ return new DatanodeFinalizationCounts(this);
+ }
+ }
}
}
diff --git
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMClientProtocolServer.java
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMClientProtocolServer.java
index ac5f17a5c6e..62301645700 100644
---
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMClientProtocolServer.java
+++
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMClientProtocolServer.java
@@ -1214,18 +1214,26 @@ public HddsProtos.UpgradeStatus queryUpgradeStatus()
throws IOException {
try {
getScm().checkAdminAccess(getRemoteUser(), true);
+ if (scm.getScmContext().isInSafeMode()) {
+ throw new SCMException("Cannot query upgrade status while SCM is in
safe mode. Wait until SCM exits "
+ + "safe mode and try again.", ResultCodes.SAFE_MODE_EXCEPTION);
+ }
+
boolean scmFinalized = !scm.getVersionManager().needsFinalization();
NodeManager.DatanodeFinalizationCounts datanodeFinalizationCounts =
scm.getScmNodeManager().getDatanodeFinalizationCounts();
int finalizedDatanodes =
datanodeFinalizationCounts.getNumFinalizedDatanodes();
int healthyDatanodes =
datanodeFinalizationCounts.getTotalHealthyDatanodes();
- boolean shouldFinalize = scmFinalized &&
datanodeFinalizationCounts.allNodesFinalized() && !scm.isInSafeMode();
+ boolean hddsFinalized = scmFinalized &&
datanodeFinalizationCounts.allNodesFinalized();
HddsProtos.UpgradeStatus result = HddsProtos.UpgradeStatus.newBuilder()
.setScmFinalized(scmFinalized)
.setNumDatanodesFinalized(finalizedDatanodes)
.setNumDatanodesTotal(healthyDatanodes)
- .setShouldFinalize(shouldFinalize)
+ .setHddsFinalized(hddsFinalized)
+
.setScmApparentVersion(scm.getVersionManager().getApparentVersion().serialize())
+
.setMinDatanodeApparentVersion(datanodeFinalizationCounts.getMinApparentVersion())
+
.setMaxDatanodeApparentVersion(datanodeFinalizationCounts.getMaxApparentVersion())
.build();
AUDIT.logReadSuccess(buildAuditMessageForSuccess(SCMAction.QUERY_UPGRADE_STATUS,
null));
diff --git
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/node/TestSCMNodeManager.java
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/node/TestSCMNodeManager.java
index 8b0ea537d4e..b7b7c8bdf8e 100644
---
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/node/TestSCMNodeManager.java
+++
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/node/TestSCMNodeManager.java
@@ -684,6 +684,36 @@ public void
testDatanodeFinalizedCounterTracksRegistrationAndRemoveNode()
}
}
+ @Test
+ public void testDatanodeFinalizationCountsTracksApparentVersionRange()
+ throws IOException, AuthenticationException {
+ try (SCMNodeManager nodeManager = createNodeManager(getConf())) {
+ // Two healthy datanodes reporting different apparent versions.
+ registerWithCapacity(nodeManager,
+ toVersionProto(HDDSLayoutFeature.INITIAL_VERSION,
HDDSVersion.SOFTWARE_VERSION), success);
+ registerWithCapacity(nodeManager, defaultVersionProto(), success);
+
+ NodeManager.DatanodeFinalizationCounts counts =
nodeManager.getDatanodeFinalizationCounts();
+ assertEquals(2, counts.getTotalHealthyDatanodes());
+ assertEquals(HDDSLayoutFeature.INITIAL_VERSION.serialize(),
counts.getMinApparentVersion(),
+ "Min apparent version should reflect the least-finalized datanode");
+ assertEquals(HDDSVersion.SOFTWARE_VERSION.serialize(),
counts.getMaxApparentVersion(),
+ "Max apparent version should reflect the most-finalized datanode");
+ }
+ }
+
+ @Test
+ public void testDatanodeFinalizationCountsWithNoHealthyDatanodes()
+ throws IOException, AuthenticationException {
+ try (SCMNodeManager nodeManager = createNodeManager(getConf())) {
+ NodeManager.DatanodeFinalizationCounts counts =
nodeManager.getDatanodeFinalizationCounts();
+ assertEquals(0, counts.getTotalHealthyDatanodes());
+ // The MAX_VALUE sentinel used to find the min must be normalized to 0
when there is nothing to count.
+ assertEquals(0, counts.getMinApparentVersion());
+ assertEquals(0, counts.getMaxApparentVersion());
+ }
+ }
+
private static Stream<Arguments> ineligibleHealthStates() {
return Stream.of(
Arguments.of(NodeStatus.inServiceStale()),
diff --git
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/TestSCMClientProtocolServer.java
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/TestSCMClientProtocolServer.java
index 61ec18c3e1c..8ac74e4b86b 100644
---
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/TestSCMClientProtocolServer.java
+++
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/server/TestSCMClientProtocolServer.java
@@ -21,7 +21,6 @@
import static
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_READONLY_ADMINISTRATORS;
import static
org.apache.hadoop.ozone.upgrade.UpgradeFinalization.Status.ALREADY_FINALIZED;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -50,12 +49,14 @@
import org.apache.hadoop.hdds.scm.HddsTestUtils;
import org.apache.hadoop.hdds.scm.container.ContainerInfo;
import org.apache.hadoop.hdds.scm.container.ContainerManagerImpl;
+import org.apache.hadoop.hdds.scm.exceptions.SCMException;
import org.apache.hadoop.hdds.scm.ha.SCMContext;
import org.apache.hadoop.hdds.scm.ha.SCMHAManagerStub;
import org.apache.hadoop.hdds.scm.ha.SCMNodeDetails;
import org.apache.hadoop.hdds.scm.pipeline.PipelineID;
import
org.apache.hadoop.hdds.scm.protocol.StorageContainerLocationProtocolServerSideTranslatorPB;
import org.apache.hadoop.hdds.scm.safemode.SCMSafeModeManager;
+import org.apache.hadoop.hdds.scm.safemode.SCMSafeModeManager.SafeModeStatus;
import org.apache.hadoop.hdds.scm.server.upgrade.FinalizationManager;
import org.apache.hadoop.hdds.scm.server.upgrade.ScmVersionManager;
import org.apache.hadoop.hdds.utils.ProtocolMessageMetrics;
@@ -284,24 +285,23 @@ public void testQueryUpgradeStatus() throws Exception {
// No datanodes registered
assertEquals(0, status.getNumDatanodesFinalized());
assertEquals(0, status.getNumDatanodesTotal());
- assertTrue(status.getShouldFinalize());
+ assertTrue(status.getHddsFinalized());
}
@Test
- public void testQueryUpgradeStatusInSafemode() throws Exception {
- // mockSafeModeManager defaults to returning true for getInSafeMode()
- when(mockSafeModeManager.getInSafeMode()).thenReturn(true);
- assertTrue(scm.isInSafeMode());
-
- HddsProtos.UpgradeStatus status = server.queryUpgradeStatus();
+ public void testQueryUpgradeStatusInSafemode() {
+ // Put SCM into safe mode via the context the server consults.
+ scm.getScmContext().updateSafeModeStatus(SafeModeStatus.INITIAL);
+ try {
+ assertTrue(scm.getScmContext().isInSafeMode());
- // SCM starts already finalized in tests
- assertTrue(status.getScmFinalized());
- // No datanodes registered
- assertEquals(0, status.getNumDatanodesFinalized());
- assertEquals(0, status.getNumDatanodesTotal());
- // shouldFinalize is false because SCM is in safe mode
- assertFalse(status.getShouldFinalize());
+ // Querying upgrade status is blocked while SCM is in safe mode.
+ SCMException ex = assertThrows(SCMException.class, () ->
server.queryUpgradeStatus());
+ assertEquals(SCMException.ResultCodes.SAFE_MODE_EXCEPTION,
ex.getResult());
+ } finally {
+ // Restore for other tests sharing the static SCM instance.
+
scm.getScmContext().updateSafeModeStatus(SafeModeStatus.OUT_OF_SAFE_MODE);
+ }
}
private ContainerInfo newContainerWithLastUsedTime(long containerId,
diff --git
a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/upgrade/FinalizeSubCommand.java
b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/upgrade/FinalizeSubCommand.java
index 411955951df..232f3347e9e 100644
---
a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/upgrade/FinalizeSubCommand.java
+++
b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/upgrade/FinalizeSubCommand.java
@@ -17,13 +17,16 @@
package org.apache.hadoop.ozone.admin.upgrade;
+import com.google.common.annotations.VisibleForTesting;
import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
import org.apache.hadoop.hdds.cli.AbstractSubcommand;
import org.apache.hadoop.hdds.cli.HddsVersionProvider;
import org.apache.hadoop.ozone.OzoneManagerVersion;
import org.apache.hadoop.ozone.admin.om.OmAddressOptions;
import org.apache.hadoop.ozone.client.rpc.RpcClient;
import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol;
+import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.QueryUpgradeStatusResponse;
import picocli.CommandLine;
/**
@@ -31,15 +34,25 @@
*/
@CommandLine.Command(
name = "finalize",
- description = "Initiates the the process to finalize a cluster upgrade.",
+ description = "Initiates the process to finalize a cluster upgrade. This
command is idempotent.",
mixinStandardHelpOptions = true,
versionProvider = HddsVersionProvider.class
)
public class FinalizeSubCommand extends AbstractSubcommand implements
Callable<Integer> {
+ /** Poll cadence used by {@code --wait}. Overridable from tests via {@link
#setPollIntervalMillis(long)}. */
+ private long pollIntervalMillis = TimeUnit.SECONDS.toMillis(5);
+
@CommandLine.Mixin
private OmAddressOptions.OptionalServiceIdOrHostMixin omAddressOptions;
+ @CommandLine.Option(names = {"--wait"},
+ defaultValue = "false",
+ description = "After initiating finalization, poll the cluster status
until the entire cluster (OM, SCM, "
+ + "and all healthy datanodes) is finalized. Interrupt with Ctrl-C to
stop waiting; finalization "
+ + "continues on the server.")
+ private boolean wait;
+
@Override
public Integer call() throws Exception {
try (OzoneManagerProtocol client = getClient()) {
@@ -50,12 +63,63 @@ public Integer call() throws Exception {
return 1;
}
client.finalizeUpgrade();
+ if (wait) {
+ out().println("Cluster finalization has been started. Waiting for the
cluster to finalize; "
+ + "interrupt with Ctrl-C to stop waiting (finalization continues
on the server).");
+ return waitForFinalization(client);
+ }
out().println("Cluster finalization has been started. Monitor progress
with `ozone admin upgrade status`");
}
return 0;
}
+ /**
+ * Polls the cluster status until OM, SCM and all healthy datanodes report
finalized, or the operator
+ * interrupts the command. A failed status query is reported to stderr and
retried on the next poll,
+ * so transient RPC errors do not abort the wait. {@code --wait} is safe to
re-run: if the cluster is
+ * already finalized, the first poll returns done and the command exits 0.
+ */
+ private int waitForFinalization(OzoneManagerProtocol client) {
+ while (true) {
+ QueryUpgradeStatusResponse status = null;
+ try {
+ status = client.queryUpgradeStatus();
+ } catch (Exception e) {
+ err().println("Failed to query upgrade status: " + e.getMessage()
+ + ". Retrying, or use `ozone admin upgrade status` to monitor
progress.");
+ }
+
+ if (status != null) {
+ // Finalization checks before sleeping, so an already-finalized
cluster returns without waiting.
+ if (status.getClusterFinalized()) {
+ out().println("Finalization complete.");
+ return 0;
+ }
+
+ if (isVerbose()) {
+ StatusSubCommand.printVerbose(status, out());
+ } else {
+ StatusSubCommand.printBasic(status, out());
+ }
+ out().flush();
+ }
+
+ try {
+ Thread.sleep(pollIntervalMillis);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ out().println("Waiting interrupted. Use `ozone admin upgrade status`
to monitor progress.");
+ return 1;
+ }
+ }
+ }
+
protected OzoneManagerProtocol getClient() throws Exception {
return omAddressOptions.newClient();
}
+
+ @VisibleForTesting
+ void setPollIntervalMillis(long millis) {
+ this.pollIntervalMillis = millis;
+ }
}
diff --git
a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/upgrade/StatusSubCommand.java
b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/upgrade/StatusSubCommand.java
index bc3a18cd1b9..fa1ef06bb58 100644
---
a/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/upgrade/StatusSubCommand.java
+++
b/hadoop-ozone/cli-admin/src/main/java/org/apache/hadoop/ozone/admin/upgrade/StatusSubCommand.java
@@ -17,14 +17,18 @@
package org.apache.hadoop.ozone.admin.upgrade;
+import java.io.PrintWriter;
import java.util.concurrent.Callable;
+import org.apache.hadoop.hdds.HDDSVersion;
import org.apache.hadoop.hdds.cli.AbstractSubcommand;
import org.apache.hadoop.hdds.cli.HddsVersionProvider;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.server.JsonUtils;
import org.apache.hadoop.ozone.OzoneManagerVersion;
import org.apache.hadoop.ozone.admin.om.OmAddressOptions;
import org.apache.hadoop.ozone.client.rpc.RpcClient;
import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol;
-import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.QueryUpgradeStatusResponse;
import picocli.CommandLine;
/**
@@ -42,6 +46,11 @@ public class StatusSubCommand extends AbstractSubcommand
implements Callable<Int
@CommandLine.Mixin
private OmAddressOptions.OptionalServiceIdOrHostMixin omAddressOptions;
+ @CommandLine.Option(names = {"--json"},
+ defaultValue = "false",
+ description = "Format output as JSON.")
+ private boolean json;
+
@Override
public Integer call() throws Exception {
try (OzoneManagerProtocol client = getClient()) {
@@ -51,20 +60,109 @@ public Integer call() throws Exception {
"`ozone admin scm finalizationstatus` and `ozone admin om
finalizationstatus`");
return 1;
}
- OzoneManagerProtocolProtos.QueryUpgradeStatusResponse status =
client.queryUpgradeStatus();
-
+ QueryUpgradeStatusResponse status = client.queryUpgradeStatus();
- out().println("Upgrade status:");
- out().println(" OM Finalized? " + status.getOmFinalized());
- out().println(" SCM Finalized? " +
status.getHddsStatus().getScmFinalized());
- out().println(" Datanodes finalized: " +
status.getHddsStatus().getNumDatanodesFinalized()
- + "/" + status.getHddsStatus().getNumDatanodesTotal());
+ if (json) {
+
out().println(JsonUtils.toJsonStringWithDefaultPrettyPrinter(UpgradeStatusDto.from(status)));
+ } else if (isVerbose()) {
+ printVerbose(status, out());
+ } else {
+ printBasic(status, out());
+ }
}
return 0;
}
+ /** Basic, non-verbose human-readable status. */
+ static void printBasic(QueryUpgradeStatusResponse status, PrintWriter out) {
+ out.println("Upgrade status:");
+ out.println(" OM Finalized? " + status.getOmFinalized());
+ out.println(" SCM Finalized? " +
status.getHddsStatus().getScmFinalized());
+ out.println(" Datanodes finalized: " +
status.getHddsStatus().getNumDatanodesFinalized()
+ + "/" + status.getHddsStatus().getNumDatanodesTotal());
+ }
+
+ /**
+ * Verbose human-readable status that includes the apparent versions
reported by OM and SCM, and the
+ * range of apparent versions across healthy datanodes.
+ */
+ static void printVerbose(QueryUpgradeStatusResponse status, PrintWriter out)
{
+ HddsProtos.UpgradeStatus hdds = status.getHddsStatus();
+ out.println("Upgrade status:");
+ out.println(" OM Finalized? " + status.getOmFinalized());
+ out.println(" OM Apparent Version: "
+ +
OzoneManagerVersion.deserialize(status.getOmApparentVersion()).toString());
+ out.println(" SCM Finalized? " + hdds.getScmFinalized());
+ out.println(" SCM Apparent Version: " +
HDDSVersion.deserialize(hdds.getScmApparentVersion()).toString());
+ out.println(" Datanodes finalized: " +
hdds.getNumDatanodesFinalized() + "/" + hdds.getNumDatanodesTotal());
+ out.println(" Min Datanode Apparent Version: "
+ +
HDDSVersion.deserialize(hdds.getMinDatanodeApparentVersion()).toString());
+ out.println(" Max Datanode Apparent Version: "
+ +
HDDSVersion.deserialize(hdds.getMaxDatanodeApparentVersion()).toString());
+ }
+
protected OzoneManagerProtocol getClient() throws Exception {
return omAddressOptions.newClient();
}
+ /**
+ * JSON-friendly DTO mirroring {@link QueryUpgradeStatusResponse}.
+ */
+ public static final class UpgradeStatusDto {
+ private boolean omFinalized;
+ private String omApparentVersion;
+ private boolean scmFinalized;
+ private String scmApparentVersion;
+ private int datanodesFinalized;
+ private int datanodesTotal;
+ private String minDatanodeApparentVersion;
+ private String maxDatanodeApparentVersion;
+
+ public static UpgradeStatusDto from(QueryUpgradeStatusResponse status) {
+ HddsProtos.UpgradeStatus hdds = status.getHddsStatus();
+ UpgradeStatusDto dto = new UpgradeStatusDto();
+ dto.omFinalized = status.getOmFinalized();
+ dto.scmFinalized = hdds.getScmFinalized();
+ dto.datanodesFinalized = hdds.getNumDatanodesFinalized();
+ dto.datanodesTotal = hdds.getNumDatanodesTotal();
+ dto.omApparentVersion =
OzoneManagerVersion.deserialize(status.getOmApparentVersion()).toString();
+ dto.scmApparentVersion =
HDDSVersion.deserialize(hdds.getScmApparentVersion()).toString();
+ dto.minDatanodeApparentVersion =
HDDSVersion.deserialize(hdds.getMinDatanodeApparentVersion()).toString();
+ dto.maxDatanodeApparentVersion =
HDDSVersion.deserialize(hdds.getMaxDatanodeApparentVersion()).toString();
+ return dto;
+ }
+
+ public boolean isOmFinalized() {
+ return omFinalized;
+ }
+
+ public String getOmApparentVersion() {
+ return omApparentVersion;
+ }
+
+ public boolean isScmFinalized() {
+ return scmFinalized;
+ }
+
+ public String getScmApparentVersion() {
+ return scmApparentVersion;
+ }
+
+ public int getDatanodesFinalized() {
+ return datanodesFinalized;
+ }
+
+ public int getDatanodesTotal() {
+ return datanodesTotal;
+ }
+
+ public String getMinDatanodeApparentVersion() {
+ return minDatanodeApparentVersion;
+ }
+
+ public String getMaxDatanodeApparentVersion() {
+ return maxDatanodeApparentVersion;
+ }
+ }
+
}
diff --git
a/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/ozone/admin/upgrade/TestFinalizeSubCommand.java
b/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/ozone/admin/upgrade/TestFinalizeSubCommand.java
index a21dca44184..056ce3b3b0c 100644
---
a/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/ozone/admin/upgrade/TestFinalizeSubCommand.java
+++
b/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/ozone/admin/upgrade/TestFinalizeSubCommand.java
@@ -18,12 +18,15 @@
package org.apache.hadoop.ozone.admin.upgrade;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -32,11 +35,14 @@
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
import org.apache.hadoop.ozone.OzoneManagerVersion;
import org.apache.hadoop.ozone.om.helpers.ServiceInfo;
import org.apache.hadoop.ozone.om.helpers.ServiceInfoEx;
import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol;
+import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.QueryUpgradeStatusResponse;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -55,6 +61,7 @@ public class TestFinalizeSubCommand {
private final PrintStream originalErr = System.err;
private FinalizeSubCommand cmd;
private OzoneManagerProtocol omClient;
+ private boolean verbose;
@BeforeEach
public void setup() throws IOException {
@@ -62,12 +69,20 @@ public void setup() throws IOException {
doNothing().when(omClient).close();
when(omClient.getServiceInfo()).thenReturn(serviceInfoWithVersion(OzoneManagerVersion.ZDU));
+ verbose = false;
cmd = new FinalizeSubCommand() {
@Override
protected OzoneManagerProtocol getClient() throws Exception {
return omClient;
}
+
+ @Override
+ protected boolean isVerbose() {
+ return verbose;
+ }
};
+ // Keep tests fast — 1 ms per poll instead of the default 5 s.
+ cmd.setPollIntervalMillis(1);
System.setOut(new PrintStream(outContent, false, DEFAULT_ENCODING));
System.setErr(new PrintStream(errContent, false, DEFAULT_ENCODING));
}
@@ -119,6 +134,191 @@ public void
testNonZduServerPrintsErrorAndReturnsNonZero() throws Exception {
verify(omClient, never()).finalizeUpgrade();
}
+ @Test
+ public void testWithoutWaitFlagDoesNotPollStatus() throws Exception {
+ new CommandLine(cmd).parseArgs();
+ assertEquals(0, cmd.call());
+
+ verify(omClient, never()).queryUpgradeStatus();
+ }
+
+ @Test
+ public void testStatusCalledOnceWhenAlreadyFinalized() throws Exception {
+ when(omClient.queryUpgradeStatus()).thenReturn(finalizedStatus(3, 3));
+
+ new CommandLine(cmd).parseArgs("--wait");
+ assertEquals(0, cmd.call());
+
+ String output = outContent.toString(DEFAULT_ENCODING);
+ assertTrue(output.contains("Finalization complete."));
+ verify(omClient).finalizeUpgrade();
+ verify(omClient, times(1)).queryUpgradeStatus();
+ }
+
+ @Test
+ public void testWaitFlagPollsUntilFinalized() throws Exception {
+ when(omClient.queryUpgradeStatus())
+ .thenReturn(inProgressStatus(0, 3))
+ .thenReturn(inProgressStatus(2, 3))
+ .thenReturn(finalizedStatus(3, 3));
+
+ new CommandLine(cmd).parseArgs("--wait");
+ assertEquals(0, cmd.call());
+
+ String output = outContent.toString(DEFAULT_ENCODING);
+ // While polling, the shared basic status output is printed on each
in-progress poll.
+ assertTrue(output.contains("Upgrade status:"));
+ assertTrue(output.contains("Finalization complete."));
+ verify(omClient, times(3)).queryUpgradeStatus();
+ }
+
+ @Test
+ public void testWaitFlagRetriesAfterQueryFailure() throws Exception {
+ // A transient query failure is reported to stderr and retried on the next
poll, not fatal.
+ when(omClient.queryUpgradeStatus())
+ .thenThrow(new IOException("RPC timeout"))
+ .thenReturn(finalizedStatus(3, 3));
+
+ new CommandLine(cmd).parseArgs("--wait");
+ assertEquals(0, cmd.call());
+
+ String errOutput = errContent.toString(DEFAULT_ENCODING);
+ assertTrue(errOutput.contains("Failed to query upgrade status"));
+ assertTrue(errOutput.contains("RPC timeout"));
+ assertTrue(outContent.toString(DEFAULT_ENCODING).contains("Finalization
complete."));
+ verify(omClient, times(2)).queryUpgradeStatus();
+ }
+
+ @Test
+ public void testWaitFlagInterruptIsHandledCleanly() throws Exception {
+ // Make the poll interval long enough that the interrupt lands during the
sleep (after the first poll).
+ cmd.setPollIntervalMillis(60_000);
+ when(omClient.queryUpgradeStatus()).thenReturn(inProgressStatus(0, 3));
+
+ new CommandLine(cmd).parseArgs("--wait");
+
+ AtomicInteger result = new AtomicInteger(-1);
+ AtomicReference<Exception> thrown = new AtomicReference<>();
+ Thread runner = new Thread(() -> {
+ try {
+ result.set(cmd.call());
+ } catch (Exception e) {
+ // The command handles interruption internally and should not throw.
+ thrown.set(e);
+ }
+ });
+ runner.start();
+ // Give the runner a moment to complete the first poll and start sleeping.
+ Thread.sleep(50);
+ runner.interrupt();
+ runner.join(5_000);
+
+ assertNull(thrown.get(), "wait command should not throw on interrupt");
+ String output = outContent.toString(DEFAULT_ENCODING);
+ assertTrue(output.contains("Waiting interrupted"));
+ // With check-before-sleep, the first poll runs before the interrupting
sleep.
+ verify(omClient, times(1)).queryUpgradeStatus();
+ // The command swallows the interrupt, but reports non-zero since
finalization was not confirmed.
+ assertEquals(1, result.get());
+ }
+
+ @Test
+ public void testWaitFlagIsResumableAfterCancel() throws Exception {
+ // First invocation: one in-progress poll, then blocked in Thread.sleep so
we can interrupt it.
+ cmd.setPollIntervalMillis(60_000);
+ // First invocation's poll: in progress (so it sleeps); second
invocation's poll: finalized.
+ when(omClient.queryUpgradeStatus())
+ .thenReturn(inProgressStatus(0, 3))
+ .thenReturn(finalizedStatus(3, 3));
+
+ new CommandLine(cmd).parseArgs("--wait");
+
+ AtomicInteger firstResult = new AtomicInteger(-1);
+ AtomicReference<Exception> thrown = new AtomicReference<>();
+ Thread runner = new Thread(() -> {
+ try {
+ firstResult.set(cmd.call());
+ } catch (Exception e) {
+ // First call should swallow the interrupt cleanly and not throw.
+ thrown.set(e);
+ }
+ });
+ runner.start();
+ Thread.sleep(50);
+ runner.interrupt();
+ runner.join(5_000);
+
+ assertNull(thrown.get(), "first wait command should not throw on
interrupt");
+ String firstOutput = outContent.toString(DEFAULT_ENCODING);
+ assertTrue(firstOutput.contains("Cluster finalization has been started"));
+ assertTrue(firstOutput.contains("Waiting interrupted"));
+ assertEquals(1, firstResult.get());
+ // First invocation performed exactly one poll before being interrupted
during the sleep.
+ verify(omClient, times(1)).queryUpgradeStatus();
+
+ // Second invocation — reset output capture and shrink the poll interval
so it exits promptly.
+ outContent.reset();
+ cmd.setPollIntervalMillis(1);
+
+ new CommandLine(cmd).parseArgs("--wait");
+ assertEquals(0, cmd.call());
+
+ String secondOutput = outContent.toString(DEFAULT_ENCODING);
+ assertTrue(secondOutput.contains("Cluster finalization has been started"));
+ assertTrue(secondOutput.contains("Finalization complete."));
+ // finalizeUpgrade must have been issued on both runs (idempotent
server-side).
+ verify(omClient, times(2)).finalizeUpgrade();
+ // queryUpgradeStatus ran once per invocation.
+ verify(omClient, times(2)).queryUpgradeStatus();
+ // The client is closed after each try-with-resources block.
+ verify(omClient, times(2)).close();
+ }
+
+ @Test
+ public void testWaitFlagWithVerbosePrintsFullStatus() throws Exception {
+ verbose = true;
+ // Return one in-progress poll first so the verbose status is printed.
+ when(omClient.queryUpgradeStatus())
+ .thenReturn(inProgressStatus(1, 2))
+ .thenReturn(finalizedStatus(2, 2));
+
+ new CommandLine(cmd).parseArgs("--wait");
+ assertEquals(0, cmd.call());
+
+ String output = outContent.toString(DEFAULT_ENCODING);
+ assertTrue(output.contains("OM Finalized?"));
+ assertTrue(output.contains("SCM Finalized?"));
+ assertTrue(output.contains("OM Apparent Version:"));
+ assertTrue(output.contains("SCM Apparent Version:"));
+ assertTrue(output.contains("Min Datanode Apparent Version:"));
+ assertFalse(output.contains("Waiting for finalization:"));
+ assertTrue(output.contains("Finalization complete."));
+ }
+
+ private static QueryUpgradeStatusResponse inProgressStatus(int dnFinalized,
int dnTotal) {
+ return QueryUpgradeStatusResponse.newBuilder()
+ .setOmFinalized(false)
+ .setClusterFinalized(false)
+ .setHddsStatus(HddsProtos.UpgradeStatus.newBuilder()
+ .setScmFinalized(false)
+ .setNumDatanodesFinalized(dnFinalized)
+ .setNumDatanodesTotal(dnTotal)
+ .build())
+ .build();
+ }
+
+ private static QueryUpgradeStatusResponse finalizedStatus(int dnFinalized,
int dnTotal) {
+ return QueryUpgradeStatusResponse.newBuilder()
+ .setOmFinalized(true)
+ .setClusterFinalized(true)
+ .setHddsStatus(HddsProtos.UpgradeStatus.newBuilder()
+ .setScmFinalized(true)
+ .setNumDatanodesFinalized(dnFinalized)
+ .setNumDatanodesTotal(dnTotal)
+ .build())
+ .build();
+ }
+
private ServiceInfoEx serviceInfoWithVersion(OzoneManagerVersion version) {
ServiceInfo serviceInfo = new ServiceInfo.Builder()
.setNodeType(HddsProtos.NodeType.OM)
diff --git
a/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/ozone/admin/upgrade/TestStatusSubCommand.java
b/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/ozone/admin/upgrade/TestStatusSubCommand.java
index 26cfef1ffe6..76ff43998e1 100644
---
a/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/ozone/admin/upgrade/TestStatusSubCommand.java
+++
b/hadoop-ozone/cli-admin/src/test/java/org/apache/hadoop/ozone/admin/upgrade/TestStatusSubCommand.java
@@ -18,6 +18,7 @@
package org.apache.hadoop.ozone.admin.upgrade;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
@@ -25,11 +26,14 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
+import org.apache.hadoop.hdds.HDDSVersion;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
import org.apache.hadoop.ozone.OzoneManagerVersion;
import org.apache.hadoop.ozone.om.helpers.ServiceInfo;
@@ -47,6 +51,7 @@
public class TestStatusSubCommand {
private static final String DEFAULT_ENCODING = StandardCharsets.UTF_8.name();
+ private static final ObjectMapper JSON = new ObjectMapper();
private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
private final ByteArrayOutputStream errContent = new ByteArrayOutputStream();
@@ -54,17 +59,24 @@ public class TestStatusSubCommand {
private final PrintStream originalErr = System.err;
private OzoneManagerProtocol omClient;
private StatusSubCommand cmd;
+ private boolean verbose;
@BeforeEach
public void setup() throws IOException {
omClient = mock(OzoneManagerProtocol.class);
when(omClient.getServiceInfo()).thenReturn(serviceInfoWithVersion(OzoneManagerVersion.ZDU));
+ verbose = false;
cmd = new StatusSubCommand() {
@Override
protected OzoneManagerProtocol getClient() throws Exception {
return omClient;
}
+
+ @Override
+ protected boolean isVerbose() {
+ return verbose;
+ }
};
System.setOut(new PrintStream(outContent, false, DEFAULT_ENCODING));
System.setErr(new PrintStream(errContent, false, DEFAULT_ENCODING));
@@ -82,7 +94,7 @@ public void testStatusCommandPrintsUpgradeStatus() throws
Exception {
.setScmFinalized(false)
.setNumDatanodesFinalized(1)
.setNumDatanodesTotal(3)
- .setShouldFinalize(true)
+ .setHddsFinalized(true)
.build();
OzoneManagerProtocolProtos.QueryUpgradeStatusResponse response =
@@ -100,6 +112,8 @@ public void testStatusCommandPrintsUpgradeStatus() throws
Exception {
assertTrue(output.contains("OM Finalized? false"));
assertTrue(output.contains("SCM Finalized? false"));
assertTrue(output.contains("Datanodes finalized: 1/3"));
+ // Without --verbose the apparent versions are not shown.
+ assertFalse(output.contains("Apparent Version"));
verify(omClient).queryUpgradeStatus();
}
@@ -122,6 +136,82 @@ public void testNonZduServerPrintsErrorAndReturnsNonZero()
throws Exception {
verify(omClient, never()).queryUpgradeStatus();
}
+ @Test
+ public void testJsonOutput() throws Exception {
+ int omVersion = OzoneManagerVersion.ZDU.serialize();
+ int hddsVersion = HDDSVersion.SOFTWARE_VERSION.serialize();
+ OzoneManagerProtocolProtos.QueryUpgradeStatusResponse response =
+ OzoneManagerProtocolProtos.QueryUpgradeStatusResponse.newBuilder()
+ .setOmFinalized(true)
+ .setOmApparentVersion(omVersion)
+ .setHddsStatus(HddsProtos.UpgradeStatus.newBuilder()
+ .setScmFinalized(true)
+ .setNumDatanodesFinalized(2)
+ .setNumDatanodesTotal(3)
+ .setScmApparentVersion(hddsVersion)
+ .setMinDatanodeApparentVersion(hddsVersion)
+ .setMaxDatanodeApparentVersion(hddsVersion)
+ .build())
+ .build();
+ when(omClient.queryUpgradeStatus()).thenReturn(response);
+
+ // JSON output includes every field, including the apparent versions.
+ new CommandLine(cmd).parseArgs("--json");
+ assertEquals(0, cmd.call());
+ String jsonOutput = outContent.toString(DEFAULT_ENCODING);
+
+ JsonNode root = JSON.readTree(jsonOutput);
+ assertTrue(root.path("omFinalized").asBoolean());
+ assertTrue(root.path("scmFinalized").asBoolean());
+ assertEquals(2, root.path("datanodesFinalized").asInt());
+ assertEquals(3, root.path("datanodesTotal").asInt());
+ assertEquals(OzoneManagerVersion.ZDU.toString(),
root.path("omApparentVersion").asText());
+ assertEquals(HDDSVersion.SOFTWARE_VERSION.toString(),
root.path("scmApparentVersion").asText());
+ assertEquals(HDDSVersion.SOFTWARE_VERSION.toString(),
root.path("minDatanodeApparentVersion").asText());
+ assertEquals(HDDSVersion.SOFTWARE_VERSION.toString(),
root.path("maxDatanodeApparentVersion").asText());
+
+ // The --verbose flag only affects the human-readable output; JSON output
is identical.
+ outContent.reset();
+ verbose = true;
+ new CommandLine(cmd).parseArgs("--json");
+ assertEquals(0, cmd.call());
+ assertEquals(jsonOutput, outContent.toString(DEFAULT_ENCODING));
+ }
+
+ @Test
+ public void testVerboseTextOutputIncludesVersions() throws Exception {
+ verbose = true;
+ int omVersion = OzoneManagerVersion.ZDU.serialize();
+ int hddsVersion = HDDSVersion.SOFTWARE_VERSION.serialize();
+ OzoneManagerProtocolProtos.QueryUpgradeStatusResponse response =
+ OzoneManagerProtocolProtos.QueryUpgradeStatusResponse.newBuilder()
+ .setOmFinalized(true)
+ .setOmApparentVersion(omVersion)
+ .setHddsStatus(HddsProtos.UpgradeStatus.newBuilder()
+ .setScmFinalized(true)
+ .setNumDatanodesFinalized(3)
+ .setNumDatanodesTotal(3)
+ .setScmApparentVersion(hddsVersion)
+ .setMinDatanodeApparentVersion(hddsVersion)
+ .setMaxDatanodeApparentVersion(hddsVersion)
+ .build())
+ .build();
+ when(omClient.queryUpgradeStatus()).thenReturn(response);
+
+ new CommandLine(cmd).parseArgs();
+ assertEquals(0, cmd.call());
+
+ String output = outContent.toString(DEFAULT_ENCODING);
+ assertTrue(output.contains("OM Finalized?"));
+ assertTrue(output.contains("OM Apparent Version:"));
+ assertTrue(output.contains("SCM Finalized?"));
+ assertTrue(output.contains("SCM Apparent Version:"));
+ assertTrue(output.contains("Min Datanode Apparent Version:"));
+ assertTrue(output.contains("Max Datanode Apparent Version:"));
+ assertTrue(output.contains(OzoneManagerVersion.ZDU.toString()));
+ assertTrue(output.contains(HDDSVersion.SOFTWARE_VERSION.toString()));
+ }
+
private ServiceInfoEx serviceInfoWithVersion(OzoneManagerVersion version) {
ServiceInfo serviceInfo = new ServiceInfo.Builder()
.setNodeType(HddsProtos.NodeType.OM)
diff --git
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/upgrade/HddsUpgradeTestUtils.java
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/upgrade/HddsUpgradeTestUtils.java
index 3f735828b4c..04defd99774 100644
---
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/upgrade/HddsUpgradeTestUtils.java
+++
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/upgrade/HddsUpgradeTestUtils.java
@@ -51,7 +51,7 @@ public static void
waitForFinalizationFromClient(StorageContainerLocationProtoco
LambdaTestUtils.await(60_000, 1_000, () -> {
HddsProtos.UpgradeStatus status = scmClient.queryUpgradeStatus();
LOG.info("Waiting for upgrade finalization to complete from client.
Current status is:\n{}", status);
- return status.getShouldFinalize();
+ return status.getHddsFinalized();
});
}
diff --git
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/OMUpgradeTestUtils.java
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/OMUpgradeTestUtils.java
index cd2d704eb46..675bfdcac67 100644
---
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/OMUpgradeTestUtils.java
+++
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/OMUpgradeTestUtils.java
@@ -17,20 +17,24 @@
package org.apache.hadoop.ozone.om;
-import static
org.apache.hadoop.ozone.upgrade.UpgradeFinalization.Status.FINALIZATION_DONE;
import static org.apache.ozone.test.GenericTestUtils.waitFor;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol;
-import org.apache.hadoop.ozone.upgrade.UpgradeFinalization;
+import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.QueryUpgradeStatusResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Utility class to help test OM upgrade scenarios.
*/
public final class OMUpgradeTestUtils {
+ private static final Logger LOG =
LoggerFactory.getLogger(OMUpgradeTestUtils.class);
+
private OMUpgradeTestUtils() {
// Utility class.
}
@@ -39,12 +43,14 @@ public static void waitForFinalization(OzoneManagerProtocol
omClient)
throws TimeoutException, InterruptedException {
waitFor(() -> {
try {
- UpgradeFinalization.StatusAndMessages statusAndMessages =
- omClient.queryUpgradeFinalizationProgress("finalize-test", false,
- false);
- System.out.println("Finalization Messages : " +
- statusAndMessages.msgs());
- return statusAndMessages.status().equals(FINALIZATION_DONE);
+ QueryUpgradeStatusResponse status = omClient.queryUpgradeStatus();
+ HddsProtos.UpgradeStatus hdds = status.getHddsStatus();
+ LOG.info("Finalization status: omFinalized={}, scmFinalized={},
datanodes={}/{}",
+ status.getOmFinalized(), hdds.getScmFinalized(),
+ hdds.getNumDatanodesFinalized(), hdds.getNumDatanodesTotal());
+ return status.getOmFinalized()
+ && hdds.getScmFinalized()
+ && hdds.getNumDatanodesFinalized() == hdds.getNumDatanodesTotal();
} catch (IOException e) {
fail(e.getMessage());
}
diff --git
a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
index cf60eb31f3a..fec82cdda36 100644
--- a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
+++ b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
@@ -1666,8 +1666,11 @@ message QueryUpgradeStatusRequest {
}
message QueryUpgradeStatusResponse {
- optional bool omFinalized = 1;
+ // True when OM, SCM and all healthy datanodes are finalized
+ optional bool clusterFinalized = 1;
optional hadoop.hdds.UpgradeStatus hddsStatus = 2;
+ optional bool omFinalized = 3;
+ optional uint32 omApparentVersion = 4;
}
// deprecated, use QueryUpgradeStatusRequest/Response instead. Retained for
wire compatibility.
diff --git
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
index 5e27bde4a2b..012e4e857a9 100644
---
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
+++
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
@@ -194,6 +194,7 @@
import org.apache.hadoop.hdds.scm.ScmInfo;
import org.apache.hadoop.hdds.scm.client.HddsClientUtils;
import org.apache.hadoop.hdds.scm.client.ScmTopologyClient;
+import org.apache.hadoop.hdds.scm.exceptions.SCMException;
import org.apache.hadoop.hdds.scm.ha.SCMNodeInfo;
import org.apache.hadoop.hdds.scm.net.NetworkTopology;
import org.apache.hadoop.hdds.scm.protocol.ScmBlockLocationProtocol;
@@ -3664,11 +3665,26 @@ public void finalizeUpgrade() throws IOException {
@Override
public QueryUpgradeStatusResponse queryUpgradeStatus() throws IOException {
- HddsProtos.UpgradeStatus scmStatus =
scmClient.getContainerClient().queryUpgradeStatus();
+ HddsProtos.UpgradeStatus scmStatus;
+ try {
+ scmStatus = scmClient.getContainerClient().queryUpgradeStatus();
+ } catch (SCMException e) {
+ // SCM refuses the query while in safe mode
+ if (e.getResult() == SCMException.ResultCodes.SAFE_MODE_EXCEPTION) {
+ throw new OMException(e.getMessage(), e, NOT_SUPPORTED_OPERATION);
+ }
+ throw e;
+ }
+
+ boolean omFinalized = !versionManager.needsFinalization();
+ boolean hddsFinalized = scmStatus.getHddsFinalized();
+ boolean clusterFinalized = omFinalized && hddsFinalized;
return QueryUpgradeStatusResponse.newBuilder()
- .setOmFinalized(!versionManager.needsFinalization())
+ .setOmFinalized(omFinalized)
.setHddsStatus(scmStatus)
+ .setOmApparentVersion(versionManager.getApparentVersion().serialize())
+ .setClusterFinalized(clusterFinalized)
.build();
}
diff --git
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/upgrade/OMUpgradeFinalizeService.java
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/upgrade/OMUpgradeFinalizeService.java
index 93926ba6594..00cc567bf3d 100644
---
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/upgrade/OMUpgradeFinalizeService.java
+++
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/upgrade/OMUpgradeFinalizeService.java
@@ -23,6 +23,7 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.scm.exceptions.SCMException;
import org.apache.hadoop.hdds.utils.BackgroundService;
import org.apache.hadoop.hdds.utils.BackgroundTask;
import org.apache.hadoop.hdds.utils.BackgroundTaskQueue;
@@ -116,7 +117,7 @@ public BackgroundTaskResult call() {
}
HddsProtos.UpgradeStatus upgradeStatus =
scmClient.getContainerClient().queryUpgradeStatus();
- if (upgradeStatus.getShouldFinalize()) {
+ if (upgradeStatus.getHddsFinalized()) {
LOG.info("The SCM Upgrade has been finalized. OM will now
finalize. Run count {}", run);
OzoneManagerProtocolProtos.OMRequest omRequest =
OzoneManagerProtocolProtos.OMRequest.newBuilder()
@@ -133,8 +134,13 @@ public BackgroundTaskResult call() {
LOG.debug("The SCM Upgrade has not been finalized. Run count {}",
run);
}
} catch (Exception e) {
- LOG.error("An exception occurred while trying to check the SCM
Upgrade status or finalize OM. Run count {}",
- run, e);
+ if (e instanceof SCMException
+ && ((SCMException) e).getResult() ==
SCMException.ResultCodes.SAFE_MODE_EXCEPTION) {
+ LOG.info("SCM is in safe mode; will retry checking the upgrade
status on the next run. Run count {}", run);
+ } else {
+ LOG.error("An exception occurred while trying to check the SCM
Upgrade status or finalize OM. Run count {}",
+ run, e);
+ }
}
} else {
LOG.debug("Finalization is not in progress. Run count {}", run);
diff --git
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/TestOMUpgradeFinalizeService.java
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/TestOMUpgradeFinalizeService.java
index a3aa7be8b5f..ef0f6ef6eb8 100644
---
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/TestOMUpgradeFinalizeService.java
+++
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/upgrade/TestOMUpgradeFinalizeService.java
@@ -125,7 +125,7 @@ void testNoTasksSubmittedWhenFinalizationNotNeeded() throws
Exception {
/**
* When the OM is the leader, finalization is needed, the finalization
command is given and SCM reports
- * shouldFinalize=true, a FinalizeUpgrade request should be submitted via
Ratis.
+ * hddsFinalized=true, a FinalizeUpgrade request should be submitted via
Ratis.
*/
@Test
void testFinalizationTriggeredWhenScmIsFinalizedAndFinalizationInProgress()
throws Exception {
@@ -134,7 +134,7 @@ void
testFinalizationTriggeredWhenScmIsFinalizedAndFinalizationInProgress() thro
HddsProtos.UpgradeStatus scmStatus = HddsProtos.UpgradeStatus.newBuilder()
.setScmFinalized(true)
- .setShouldFinalize(true)
+ .setHddsFinalized(true)
.setNumDatanodesFinalized(3)
.setNumDatanodesTotal(3)
.build();
@@ -156,7 +156,7 @@ void
testFinalizationTriggeredWhenScmIsFinalizedAndFinalizationInProgress() thro
}
/**
- * When SCM reports shouldFinalize=false (SCM is not yet finalized),
+ * When SCM reports hddsFinalized=false (SCM is not yet finalized),
* no Ratis request should be submitted.
*/
@Test
@@ -166,7 +166,7 @@ void testFinalizationSkippedWhenScmNotYetFinalized() throws
Exception {
HddsProtos.UpgradeStatus scmStatus = HddsProtos.UpgradeStatus.newBuilder()
.setScmFinalized(false)
- .setShouldFinalize(false)
+ .setHddsFinalized(false)
.setNumDatanodesFinalized(0)
.setNumDatanodesTotal(3)
.build();
@@ -248,7 +248,7 @@ void testExceptionFromRatisSubmitIsHandledGracefully()
throws Exception {
HddsProtos.UpgradeStatus scmStatus = HddsProtos.UpgradeStatus.newBuilder()
.setScmFinalized(true)
- .setShouldFinalize(true)
+ .setHddsFinalized(true)
.setNumDatanodesFinalized(3)
.setNumDatanodesTotal(3)
.build();
diff --git
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/protocolPB/TestOzoneManagerRequestHandler.java
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/protocolPB/TestOzoneManagerRequestHandler.java
index a2c51a7e88c..764a8286648 100644
---
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/protocolPB/TestOzoneManagerRequestHandler.java
+++
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/protocolPB/TestOzoneManagerRequestHandler.java
@@ -476,7 +476,7 @@ public void testQueryUpgradeStatusDispatch() throws
IOException {
HddsProtos.UpgradeStatus hddsStatus = HddsProtos.UpgradeStatus.newBuilder()
.setScmFinalized(true)
- .setShouldFinalize(false)
+ .setHddsFinalized(false)
.setNumDatanodesFinalized(3)
.setNumDatanodesTotal(3)
.build();
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]