From 13a79f82fca006b6e3c7e68137bc79b973933dc4 Mon Sep 17 00:00:00 2001 From: S O'Donnell Date: Mon, 22 Jun 2026 12:52:11 +0100 Subject: [PATCH 1/9] Initial changes --- .../ozone/admin/upgrade/StatusSubCommand.java | 42 ++++++++++++------- .../admin/upgrade/TestStatusSubCommand.java | 38 ++++++++++++----- .../java/org/apache/hadoop/ozone/OmUtils.java | 2 + .../om/protocol/OzoneManagerProtocol.java | 8 ++++ ...ManagerProtocolClientSideTranslatorPB.java | 12 ++++++ .../src/main/proto/OmClientProtocol.proto | 11 +++++ .../apache/hadoop/ozone/om/OzoneManager.java | 11 +++++ 7 files changed, 100 insertions(+), 24 deletions(-) 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 b2654e5203d7..10c38c30be37 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,32 +17,46 @@ package org.apache.hadoop.ozone.admin.upgrade; -import java.io.IOException; +import java.util.concurrent.Callable; +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.scm.cli.ScmSubcommand; -import org.apache.hadoop.hdds.scm.client.ScmClient; +import org.apache.hadoop.ozone.admin.om.OmAddressOptions; +import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos; import picocli.CommandLine; /** * Sub command to query the overall upgrade status of the cluster, returning information about the finalization - * status of SCM, the datanodes and OM. + * status of SCM, the datanodes and OM. The command makes a single call to OM which returns the status of the other + * components as well as itself. */ @CommandLine.Command( name = "status", description = "Show status of the cluster upgrade", mixinStandardHelpOptions = true, versionProvider = HddsVersionProvider.class) -public class StatusSubCommand extends ScmSubcommand { +public class StatusSubCommand extends AbstractSubcommand implements Callable { + + @CommandLine.Mixin + private OmAddressOptions.OptionalServiceIdOrHostMixin omAddressOptions; @Override - public void execute(ScmClient client) throws IOException { - HddsProtos.UpgradeStatus status = client.queryUpgradeStatus(); - - out().println("Upgrade status:"); - out().println(" SCM Finalized: " + status.getScmFinalized()); - out().println(" Datanodes finalized: " + status.getNumDatanodesFinalized()); - out().println(" Total Datanodes: " + status.getNumDatanodesTotal()); - out().println(" Should Finalize: " + status.getShouldFinalize()); + public Integer call() throws Exception { + try (OzoneManagerProtocol client = getClient()) { + OzoneManagerProtocolProtos.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()); + out().println(" Total Datanodes: " + status.getHddsStatus().getNumDatanodesTotal()); + out().println(" Should Finalize: " + status.getHddsStatus().getShouldFinalize()); + } + return 0; } + + protected OzoneManagerProtocol getClient() throws Exception { + return omAddressOptions.newClient(); + } + } 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 d0e0a439d1a0..48de456cab9b 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.assertTrue; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -25,10 +26,10 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; -import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import org.apache.hadoop.hdds.protocol.proto.HddsProtos; -import org.apache.hadoop.hdds.scm.client.ScmClient; +import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -43,11 +44,22 @@ public class TestStatusSubCommand { private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); private final PrintStream originalOut = System.out; + private OzoneManagerProtocol omClient; private StatusSubCommand cmd; @BeforeEach - public void setup() throws UnsupportedEncodingException { - cmd = new StatusSubCommand(); + public void setup() throws IOException { + omClient = mock(OzoneManagerProtocol.class); + + // Mock close() to do nothing - needed for try-with-resources + doNothing().when(omClient).close(); + + cmd = new StatusSubCommand() { + @Override + protected OzoneManagerProtocol getClient() throws Exception { + return omClient; + } + }; System.setOut(new PrintStream(outContent, false, DEFAULT_ENCODING)); } @@ -57,25 +69,31 @@ public void tearDown() { } @Test - public void testStatusCommandPrintsUpgradeStatus() throws IOException { - ScmClient scmClient = mock(ScmClient.class); - HddsProtos.UpgradeStatus status = HddsProtos.UpgradeStatus.newBuilder() + public void testStatusCommandPrintsUpgradeStatus() throws Exception { + HddsProtos.UpgradeStatus hddsStatus = HddsProtos.UpgradeStatus.newBuilder() .setScmFinalized(false) .setNumDatanodesFinalized(1) .setNumDatanodesTotal(3) .setShouldFinalize(true) .build(); - when(scmClient.queryUpgradeStatus()).thenReturn(status); + OzoneManagerProtocolProtos.QueryUpgradeStatusResponse response = + OzoneManagerProtocolProtos.QueryUpgradeStatusResponse.newBuilder() + .setOmFinalized(false) + .setHddsStatus(hddsStatus) + .build(); + + when(omClient.queryUpgradeStatus()).thenReturn(response); new CommandLine(cmd).parseArgs(); - cmd.execute(scmClient); + cmd.call(); String output = outContent.toString(DEFAULT_ENCODING); assertTrue(output.contains("Upgrade status:")); + assertTrue(output.contains("OM Finalized: false")); assertTrue(output.contains("SCM Finalized: false")); assertTrue(output.contains("Datanodes finalized: 1")); assertTrue(output.contains("Total Datanodes: 3")); assertTrue(output.contains("Should Finalize: true")); - verify(scmClient).queryUpgradeStatus(); + verify(omClient).queryUpgradeStatus(); } } diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java index 23e1d3c83822..b324fa766655 100644 --- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java +++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java @@ -261,6 +261,7 @@ public static boolean isReadOnly(OMRequest omRequest) { case GetObjectTagging: case GetQuotaRepairStatus: case StartQuotaRepair: + case QueryUpgradeStatus: return true; case CreateVolume: case SetVolumeProperty: @@ -455,6 +456,7 @@ public static boolean shouldSendToFollower(OMRequest omRequest) { // Quota repair lifecycle request should be initiated by the leader case DBUpdates: // We are currently only interested on the leader DB info case UnknownCommand: + case QueryUpgradeStatus: return false; case EchoRPC: return omRequest.getEchoRPCRequest().getReadOnly(); diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java index 30b0c1404021..4830b5f6402d 100644 --- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java +++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java @@ -507,6 +507,14 @@ ListOpenFilesResult listOpenFiles(String path, int maxKeys, String contToken) */ void finalizeUpgrade() throws IOException; + /** + * Returns the upgrade status of the cluster. This call is received by OM which will in turn query SCM to get the + * status of it and the datanodes, and return the combined status to the caller. + * @return QueryUpgradeStatusResponse containing details of the overall cluster state + * @throws IOException If any error occurs. + */ + OzoneManagerProtocolProtos.QueryUpgradeStatusResponse queryUpgradeStatus() throws IOException; + /** * Queries the current status of finalization. * This method when called, returns the status messages from the finalization diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java index be95623fb901..27b0e4056bd5 100644 --- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java +++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java @@ -2073,6 +2073,18 @@ public StatusAndMessages queryUpgradeFinalizationProgress( ); } + @Override + public OzoneManagerProtocolProtos.QueryUpgradeStatusResponse queryUpgradeStatus() throws IOException { + OzoneManagerProtocolProtos.QueryUpgradeStatusRequest + req = OzoneManagerProtocolProtos.QueryUpgradeStatusRequest.newBuilder().build(); + + OMRequest omRequest = createOMRequest(Type.QueryUpgradeStatus) + .setQueryUpgradeStatusRequest(req) + .build(); + + return handleError(submitRequest(omRequest)).getQueryUpgradeStatusResponse(); + } + /** * Get a valid Delegation Token. * diff --git a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto index 7efd46983bc9..22b32432c051 100644 --- a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto +++ b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto @@ -158,6 +158,7 @@ enum Type { DeleteObjectTagging = 142; SubmitSnapshotDiff = 143; StartFinalizeUpgrade = 144; + QueryUpgradeStatus = 145; } enum SafeMode { @@ -311,6 +312,7 @@ message OMRequest { optional SubmitSnapshotDiffRequest submitSnapshotDiffRequest = 144; optional StartFinalizeUpgradeRequest startFinalizeUpgradeRequest = 145; + optional QueryUpgradeStatusRequest queryUpgradeStatusRequest = 146; } message OMResponse { @@ -447,6 +449,7 @@ message OMResponse { optional SubmitSnapshotDiffResponse submitSnapshotDiffResponse = 143; optional StartFinalizeUpgradeResponse startFinalizeUpgradeResponse = 144; + optional QueryUpgradeStatusResponse queryUpgradeStatusResponse = 145; } enum Status { @@ -1640,6 +1643,14 @@ message StartFinalizeUpgradeRequest { message StartFinalizeUpgradeResponse { } +message QueryUpgradeStatusRequest { +} + +message QueryUpgradeStatusResponse { + required bool omFinalized = 1; + required hadoop.hdds.UpgradeStatus hddsStatus = 2; +} + message FinalizeUpgradeProgressRequest { // Ignored by OM; retained for wire compatibility. required string upgradeClientId = 1; 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 8d99f122e49c..6010eaa47975 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 @@ -309,6 +309,7 @@ import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.ExtendedUserAccessIdInfo; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.KeyArgs; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRoleInfo; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.QueryUpgradeStatusResponse; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.S3Authentication; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.ServicePort; import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.TenantState; @@ -3648,6 +3649,16 @@ public void finalizeUpgrade() throws IOException { throw new UnsupportedOperationException(); } + @Override + public QueryUpgradeStatusResponse queryUpgradeStatus() throws IOException { + HddsProtos.UpgradeStatus scmStatus = scmClient.getContainerClient().queryUpgradeStatus(); + + return QueryUpgradeStatusResponse.newBuilder() + .setOmFinalized(!versionManager.needsFinalization()) + .setHddsStatus(scmStatus) + .build(); + } + @Override public StatusAndMessages queryUpgradeFinalizationProgress( String unusedUpgradeClientId, boolean unusedTakeover, boolean unusedReadonly) From 7c8d1cab86c093f0ef45f9d255bb6a994974de7e Mon Sep 17 00:00:00 2001 From: S O'Donnell Date: Mon, 22 Jun 2026 13:06:14 +0100 Subject: [PATCH 2/9] Add new command to the request processing --- .../hadoop/ozone/admin/upgrade/TestStatusSubCommand.java | 4 ---- .../hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java | 5 +++++ 2 files changed, 5 insertions(+), 4 deletions(-) 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 48de456cab9b..ccaadd15ab44 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,7 +18,6 @@ package org.apache.hadoop.ozone.admin.upgrade; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -51,9 +50,6 @@ public class TestStatusSubCommand { public void setup() throws IOException { omClient = mock(OzoneManagerProtocol.class); - // Mock close() to do nothing - needed for try-with-resources - doNothing().when(omClient).close(); - cmd = new StatusSubCommand() { @Override protected OzoneManagerProtocol getClient() throws Exception { diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java index 738eaa4cad8a..5b6f46235395 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java @@ -398,6 +398,11 @@ public OMResponse handleReadRequest(OMRequest request) { getObjectTagging(request.getGetObjectTaggingRequest()); responseBuilder.setGetObjectTaggingResponse(getObjectTaggingResponse); break; + case QueryUpgradeStatus: + OzoneManagerProtocolProtos.QueryUpgradeStatusResponse queryUpgradeStatusResponse = + getOzoneManager().queryUpgradeStatus(); + responseBuilder.setQueryUpgradeStatusResponse(queryUpgradeStatusResponse); + break; default: responseBuilder.setSuccess(false); responseBuilder.setMessage("Unrecognized Command Type: " + cmdType); From 76240d5ee3ea3f6818254b6c0617f4009ab6e3d2 Mon Sep 17 00:00:00 2001 From: S O'Donnell Date: Mon, 22 Jun 2026 13:23:12 +0100 Subject: [PATCH 3/9] Add more tests --- .../admin/upgrade/TestStatusSubCommand.java | 8 ++ .../TestOzoneManagerQueryUpgradeStatus.java | 103 ++++++++++++++++++ .../TestOzoneManagerRequestHandler.java | 32 ++++++ 3 files changed, 143 insertions(+) create mode 100644 hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOzoneManagerQueryUpgradeStatus.java 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 ccaadd15ab44..01096a8d79fd 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 @@ -17,6 +17,7 @@ package org.apache.hadoop.ozone.admin.upgrade; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -92,4 +93,11 @@ public void testStatusCommandPrintsUpgradeStatus() throws Exception { assertTrue(output.contains("Should Finalize: true")); verify(omClient).queryUpgradeStatus(); } + + @Test + public void testStatusCommandPropagatesException() throws Exception { + when(omClient.queryUpgradeStatus()).thenThrow(new IOException("OM unavailable")); + new CommandLine(cmd).parseArgs(); + assertThrows(IOException.class, () -> cmd.call()); + } } diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOzoneManagerQueryUpgradeStatus.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOzoneManagerQueryUpgradeStatus.java new file mode 100644 index 000000000000..302879d92a4f --- /dev/null +++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOzoneManagerQueryUpgradeStatus.java @@ -0,0 +1,103 @@ +/* + * 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.om; + +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; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import org.apache.hadoop.hdds.protocol.proto.HddsProtos; +import org.apache.hadoop.hdds.scm.HddsWhiteboxTestUtils; +import org.apache.hadoop.hdds.scm.protocol.StorageContainerLocationProtocol; +import org.apache.hadoop.ozone.om.upgrade.OMVersionManager; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.QueryUpgradeStatusResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +/** + * Unit tests for {@link OzoneManager#queryUpgradeStatus()}. + */ +public class TestOzoneManagerQueryUpgradeStatus { + + private OzoneManager ozoneManager; + private OMVersionManager versionManager; + private StorageContainerLocationProtocol containerClient; + + @BeforeEach + public void setup() { + ozoneManager = Mockito.mock(OzoneManager.class, Mockito.CALLS_REAL_METHODS); + + ScmClient scmClient = mock(ScmClient.class); + containerClient = mock(StorageContainerLocationProtocol.class); + when(scmClient.getContainerClient()).thenReturn(containerClient); + + versionManager = mock(OMVersionManager.class); + + HddsWhiteboxTestUtils.setInternalState(ozoneManager, "scmClient", scmClient); + HddsWhiteboxTestUtils.setInternalState(ozoneManager, "versionManager", versionManager); + } + + @Test + public void testOmFinalizedWhenFinalizationNotNeeded() throws IOException { + when(versionManager.needsFinalization()).thenReturn(false); + when(containerClient.queryUpgradeStatus()).thenReturn(HddsProtos.UpgradeStatus.getDefaultInstance()); + + QueryUpgradeStatusResponse response = ozoneManager.queryUpgradeStatus(); + + assertTrue(response.getOmFinalized()); + } + + @Test + public void testOmNotFinalizedWhenFinalizationNeeded() throws IOException { + when(versionManager.needsFinalization()).thenReturn(true); + when(containerClient.queryUpgradeStatus()).thenReturn(HddsProtos.UpgradeStatus.getDefaultInstance()); + + QueryUpgradeStatusResponse response = ozoneManager.queryUpgradeStatus(); + + assertFalse(response.getOmFinalized()); + } + + @Test + public void testScmStatusPassedThrough() throws IOException { + HddsProtos.UpgradeStatus scmStatus = HddsProtos.UpgradeStatus.newBuilder() + .setScmFinalized(true) + .setShouldFinalize(false) + .setNumDatanodesFinalized(5) + .setNumDatanodesTotal(5) + .build(); + when(versionManager.needsFinalization()).thenReturn(false); + when(containerClient.queryUpgradeStatus()).thenReturn(scmStatus); + + QueryUpgradeStatusResponse response = ozoneManager.queryUpgradeStatus(); + + assertEquals(scmStatus, response.getHddsStatus()); + } + + @Test + public void testScmExceptionPropagates() throws IOException { + when(versionManager.needsFinalization()).thenReturn(false); + when(containerClient.queryUpgradeStatus()).thenThrow(new IOException("SCM unreachable")); + + assertThrows(IOException.class, () -> ozoneManager.queryUpgradeStatus()); + } +} 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 5424a970dc78..6e99f949c154 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 @@ -317,6 +317,38 @@ public void testWriteRequestExceptionAuditLog() { "Audit message should contain Command CreateVolume, got: " + formatted); } + @Test + public void testQueryUpgradeStatusDispatch() throws IOException { + OzoneManagerRequestHandler handler = getRequestHandler(10); + OzoneManager ozoneManager = handler.getOzoneManager(); + + HddsProtos.UpgradeStatus hddsStatus = HddsProtos.UpgradeStatus.newBuilder() + .setScmFinalized(true) + .setShouldFinalize(false) + .setNumDatanodesFinalized(3) + .setNumDatanodesTotal(3) + .build(); + OzoneManagerProtocolProtos.QueryUpgradeStatusResponse expected = + OzoneManagerProtocolProtos.QueryUpgradeStatusResponse.newBuilder() + .setOmFinalized(true) + .setHddsStatus(hddsStatus) + .build(); + Mockito.when(ozoneManager.queryUpgradeStatus()).thenReturn(expected); + + OzoneManagerProtocolProtos.OMRequest request = + OzoneManagerProtocolProtos.OMRequest.newBuilder() + .setCmdType(OzoneManagerProtocolProtos.Type.QueryUpgradeStatus) + .setClientId("test-client") + .build(); + + OzoneManagerProtocolProtos.OMResponse response = handler.handleReadRequest(request); + + Assertions.assertTrue(response.getSuccess()); + Assertions.assertTrue(response.hasQueryUpgradeStatusResponse()); + Assertions.assertEquals(expected, response.getQueryUpgradeStatusResponse()); + Mockito.verify(ozoneManager).queryUpgradeStatus(); + } + @Test public void testSnapshotDiffRoutingUsesCorrectServerMethodBasedOnOptionalFlags() throws IOException { From c17f3bd1460dfcdd3cbf9396fe021347a8fbb374 Mon Sep 17 00:00:00 2001 From: S O'Donnell Date: Mon, 22 Jun 2026 16:05:48 +0100 Subject: [PATCH 4/9] Add server version check --- .../ozone/admin/upgrade/StatusSubCommand.java | 8 +++++ .../admin/upgrade/TestStatusSubCommand.java | 31 +++++++++++++++++++ 2 files changed, 39 insertions(+) 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 10c38c30be37..71c8b4602011 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 @@ -20,7 +20,9 @@ import java.util.concurrent.Callable; 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; import picocli.CommandLine; @@ -43,6 +45,12 @@ public class StatusSubCommand extends AbstractSubcommand implements Callable cmd.call()); } + + @Test + public void testNonZduServerPrintsErrorAndReturnsNonZero() throws Exception { + when(omClient.getServiceInfo()).thenReturn(serviceInfoWithVersion(OzoneManagerVersion.DEFAULT_VERSION)); + + new CommandLine(cmd).parseArgs(); + assertEquals(1, cmd.call()); + + String errOutput = errContent.toString(DEFAULT_ENCODING); + assertTrue(errOutput.contains("OM does not support ZDU")); + verify(omClient, never()).finalizeUpgrade(); + } + + private ServiceInfoEx serviceInfoWithVersion(OzoneManagerVersion version) { + ServiceInfo serviceInfo = new ServiceInfo.Builder() + .setNodeType(HddsProtos.NodeType.OM) + .setHostname("localhost") + .setOmVersion(version) + .build(); + return new ServiceInfoEx(Collections.singletonList(serviceInfo), "", Collections.emptyList()); + } } From 87c529867be20bde2a05eb29973b959fcef3bfcb Mon Sep 17 00:00:00 2001 From: S O'Donnell Date: Mon, 22 Jun 2026 16:10:26 +0100 Subject: [PATCH 5/9] Add deprecated labels to legacy query command protobuf --- .../interface-client/src/main/proto/OmClientProtocol.proto | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto index 22b32432c051..89c149694881 100644 --- a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto +++ b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto @@ -218,7 +218,7 @@ message OMRequest { optional ServiceListRequest serviceListRequest = 51; optional DBUpdatesRequest dbUpdatesRequest = 53; optional FinalizeUpgradeRequest finalizeUpgradeRequest = 54 [deprecated = true]; - optional FinalizeUpgradeProgressRequest finalizeUpgradeProgressRequest = 55; + optional FinalizeUpgradeProgressRequest finalizeUpgradeProgressRequest = 55 [deprecated = true]; optional PrepareRequest prepareRequest = 56; optional PrepareStatusRequest prepareStatusRequest = 57; optional CancelPrepareRequest cancelPrepareRequest = 58; @@ -363,7 +363,7 @@ message OMResponse { optional ServiceListResponse ServiceListResponse = 51; optional DBUpdatesResponse dbUpdatesResponse = 52; optional FinalizeUpgradeResponse finalizeUpgradeResponse = 54 [deprecated = true]; - optional FinalizeUpgradeProgressResponse finalizeUpgradeProgressResponse = 55; + optional FinalizeUpgradeProgressResponse finalizeUpgradeProgressResponse = 55 [deprecated = true]; optional PrepareResponse prepareResponse = 56; optional PrepareStatusResponse prepareStatusResponse = 57; optional CancelPrepareResponse cancelPrepareResponse = 58; @@ -1651,6 +1651,7 @@ message QueryUpgradeStatusResponse { required hadoop.hdds.UpgradeStatus hddsStatus = 2; } +// deprecated, use QueryUpgradeStatusRequest/Response instead. Retained for wire compatibility. message FinalizeUpgradeProgressRequest { // Ignored by OM; retained for wire compatibility. required string upgradeClientId = 1; From d2257230b47fb85030c71e29455f8c98e3a7d398 Mon Sep 17 00:00:00 2001 From: S O'Donnell Date: Mon, 22 Jun 2026 16:27:58 +0100 Subject: [PATCH 6/9] Fix pmd --- .../apache/hadoop/ozone/admin/upgrade/TestStatusSubCommand.java | 1 + 1 file changed, 1 insertion(+) 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 a8c5187aa3e4..04a853da5d09 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 @@ -73,6 +73,7 @@ protected OzoneManagerProtocol getClient() throws Exception { @AfterEach public void tearDown() { System.setOut(originalOut); + System.setErr(originalErr); } @Test From f9cc6c06c2cd6c6b159406d781230e447048a528 Mon Sep 17 00:00:00 2001 From: S O'Donnell Date: Fri, 26 Jun 2026 12:00:36 +0100 Subject: [PATCH 7/9] Address review comments --- .../ozone/admin/upgrade/StatusSubCommand.java | 14 +++++++------- .../ozone/admin/upgrade/TestStatusSubCommand.java | 2 +- .../src/main/proto/OmClientProtocol.proto | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) 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 71c8b4602011..bc3a18cd1b94 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 @@ -47,18 +47,18 @@ public Integer call() throws Exception { try (OzoneManagerProtocol client = getClient()) { OzoneManagerVersion omVersion = RpcClient.getOmVersion(client.getServiceInfo()); if (!OzoneManagerVersion.ZDU.isSupportedBy(omVersion)) { - err().println("OM does not support ZDU. The cluster upgrade status should be queried with the pre ZDU " + - "commands, eg `ozone admin scm finalizationstatus` and `ozone admin om finalizationstatus`"); + err().println("OM does not support zero downtime upgrade. The cluster upgrade status should be queried with " + + "`ozone admin scm finalizationstatus` and `ozone admin om finalizationstatus`"); return 1; } OzoneManagerProtocolProtos.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()); - out().println(" Total Datanodes: " + status.getHddsStatus().getNumDatanodesTotal()); - out().println(" Should Finalize: " + status.getHddsStatus().getShouldFinalize()); + out().println(" OM Finalized? " + status.getOmFinalized()); + out().println(" SCM Finalized? " + status.getHddsStatus().getScmFinalized()); + out().println(" Datanodes finalized: " + status.getHddsStatus().getNumDatanodesFinalized() + + "/" + status.getHddsStatus().getNumDatanodesTotal()); } return 0; } 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 04a853da5d09..cdc83b9c0394 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 @@ -121,7 +121,7 @@ public void testNonZduServerPrintsErrorAndReturnsNonZero() throws Exception { String errOutput = errContent.toString(DEFAULT_ENCODING); assertTrue(errOutput.contains("OM does not support ZDU")); - verify(omClient, never()).finalizeUpgrade(); + verify(omClient, never()).queryUpgradeStatus(); } private ServiceInfoEx serviceInfoWithVersion(OzoneManagerVersion version) { diff --git a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto index 89c149694881..a2d27329fc0f 100644 --- a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto +++ b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto @@ -1647,8 +1647,8 @@ message QueryUpgradeStatusRequest { } message QueryUpgradeStatusResponse { - required bool omFinalized = 1; - required hadoop.hdds.UpgradeStatus hddsStatus = 2; + optional bool omFinalized = 1; + optional hadoop.hdds.UpgradeStatus hddsStatus = 2; } // deprecated, use QueryUpgradeStatusRequest/Response instead. Retained for wire compatibility. From b82a3039d76ad5115e85869304c88ebee69a19bc Mon Sep 17 00:00:00 2001 From: S O'Donnell Date: Fri, 26 Jun 2026 12:00:54 +0100 Subject: [PATCH 8/9] Remove test class --- .../TestOzoneManagerQueryUpgradeStatus.java | 103 ------------------ 1 file changed, 103 deletions(-) delete mode 100644 hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOzoneManagerQueryUpgradeStatus.java diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOzoneManagerQueryUpgradeStatus.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOzoneManagerQueryUpgradeStatus.java deleted file mode 100644 index 302879d92a4f..000000000000 --- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOzoneManagerQueryUpgradeStatus.java +++ /dev/null @@ -1,103 +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.om; - -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; -import static org.mockito.Mockito.when; - -import java.io.IOException; -import org.apache.hadoop.hdds.protocol.proto.HddsProtos; -import org.apache.hadoop.hdds.scm.HddsWhiteboxTestUtils; -import org.apache.hadoop.hdds.scm.protocol.StorageContainerLocationProtocol; -import org.apache.hadoop.ozone.om.upgrade.OMVersionManager; -import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.QueryUpgradeStatusResponse; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -/** - * Unit tests for {@link OzoneManager#queryUpgradeStatus()}. - */ -public class TestOzoneManagerQueryUpgradeStatus { - - private OzoneManager ozoneManager; - private OMVersionManager versionManager; - private StorageContainerLocationProtocol containerClient; - - @BeforeEach - public void setup() { - ozoneManager = Mockito.mock(OzoneManager.class, Mockito.CALLS_REAL_METHODS); - - ScmClient scmClient = mock(ScmClient.class); - containerClient = mock(StorageContainerLocationProtocol.class); - when(scmClient.getContainerClient()).thenReturn(containerClient); - - versionManager = mock(OMVersionManager.class); - - HddsWhiteboxTestUtils.setInternalState(ozoneManager, "scmClient", scmClient); - HddsWhiteboxTestUtils.setInternalState(ozoneManager, "versionManager", versionManager); - } - - @Test - public void testOmFinalizedWhenFinalizationNotNeeded() throws IOException { - when(versionManager.needsFinalization()).thenReturn(false); - when(containerClient.queryUpgradeStatus()).thenReturn(HddsProtos.UpgradeStatus.getDefaultInstance()); - - QueryUpgradeStatusResponse response = ozoneManager.queryUpgradeStatus(); - - assertTrue(response.getOmFinalized()); - } - - @Test - public void testOmNotFinalizedWhenFinalizationNeeded() throws IOException { - when(versionManager.needsFinalization()).thenReturn(true); - when(containerClient.queryUpgradeStatus()).thenReturn(HddsProtos.UpgradeStatus.getDefaultInstance()); - - QueryUpgradeStatusResponse response = ozoneManager.queryUpgradeStatus(); - - assertFalse(response.getOmFinalized()); - } - - @Test - public void testScmStatusPassedThrough() throws IOException { - HddsProtos.UpgradeStatus scmStatus = HddsProtos.UpgradeStatus.newBuilder() - .setScmFinalized(true) - .setShouldFinalize(false) - .setNumDatanodesFinalized(5) - .setNumDatanodesTotal(5) - .build(); - when(versionManager.needsFinalization()).thenReturn(false); - when(containerClient.queryUpgradeStatus()).thenReturn(scmStatus); - - QueryUpgradeStatusResponse response = ozoneManager.queryUpgradeStatus(); - - assertEquals(scmStatus, response.getHddsStatus()); - } - - @Test - public void testScmExceptionPropagates() throws IOException { - when(versionManager.needsFinalization()).thenReturn(false); - when(containerClient.queryUpgradeStatus()).thenThrow(new IOException("SCM unreachable")); - - assertThrows(IOException.class, () -> ozoneManager.queryUpgradeStatus()); - } -} From b9f1f2ca18c1d8d150f74b528aa76007f42082eb Mon Sep 17 00:00:00 2001 From: S O'Donnell Date: Fri, 26 Jun 2026 12:02:16 +0100 Subject: [PATCH 9/9] Fix tests --- .../ozone/admin/upgrade/TestStatusSubCommand.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) 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 cdc83b9c0394..26cfef1ffe63 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 @@ -96,12 +96,10 @@ public void testStatusCommandPrintsUpgradeStatus() throws Exception { cmd.call(); String output = outContent.toString(DEFAULT_ENCODING); - assertTrue(output.contains("Upgrade status:")); - assertTrue(output.contains("OM Finalized: false")); - assertTrue(output.contains("SCM Finalized: false")); - assertTrue(output.contains("Datanodes finalized: 1")); - assertTrue(output.contains("Total Datanodes: 3")); - assertTrue(output.contains("Should Finalize: true")); + assertTrue(output.contains("Upgrade status")); + assertTrue(output.contains("OM Finalized? false")); + assertTrue(output.contains("SCM Finalized? false")); + assertTrue(output.contains("Datanodes finalized: 1/3")); verify(omClient).queryUpgradeStatus(); } @@ -120,7 +118,7 @@ public void testNonZduServerPrintsErrorAndReturnsNonZero() throws Exception { assertEquals(1, cmd.call()); String errOutput = errContent.toString(DEFAULT_ENCODING); - assertTrue(errOutput.contains("OM does not support ZDU")); + assertTrue(errOutput.contains("OM does not support zero downtime upgrade")); verify(omClient, never()).queryUpgradeStatus(); }