This is an automated email from the ASF dual-hosted git repository.
sammichen pushed a commit to branch HDDS-8342
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/HDDS-8342 by this push:
new b16ea44f588 HDDS-12791. Make Retention Service suspendable (#9426)
b16ea44f588 is described below
commit b16ea44f588434091f8b9b2ae2f776f757ac2f2f
Author: XiChen <[email protected]>
AuthorDate: Mon Dec 29 18:42:02 2025 +0800
HDDS-12791. Make Retention Service suspendable (#9426)
---
.../main/java/org/apache/hadoop/ozone/OmUtils.java | 2 +
.../ozone/om/protocol/OzoneManagerProtocol.java | 29 ++++++
...OzoneManagerProtocolClientSideTranslatorPB.java | 44 +++++++++
.../hadoop/ozone/shell/TestOzoneShellHA.java | 76 +++++++++++++++
.../src/main/proto/OmClientProtocol.proto | 25 +++++
.../org/apache/hadoop/ozone/audit/OMAction.java | 4 +-
.../org/apache/hadoop/ozone/om/OzoneManager.java | 16 ++++
.../hadoop/ozone/om/helpers/OMAuditLogger.java | 1 +
.../om/ratis/utils/OzoneManagerRatisUtils.java | 3 +
.../OMLifecycleSetServiceStatusRequest.java | 103 +++++++++++++++++++++
.../OMLifecycleSetServiceStatusResponse.java | 46 +++++++++
.../ozone/om/service/KeyLifecycleService.java | 41 ++++++--
.../protocolPB/OzoneManagerRequestHandler.java | 7 ++
.../ozone/om/response/TestCleanupTableInfo.java | 2 +
.../ozone/om/service/TestKeyLifecycleService.java | 41 ++++++++
.../ozone/admin/om/LifecycleResumeSubCommand.java | 73 +++++++++++++++
.../ozone/admin/om/LifecycleStatusSubCommand.java | 89 ++++++++++++++++++
.../hadoop/ozone/admin/om/LifecycleSubCommand.java | 47 ++++++++++
.../ozone/admin/om/LifecycleSuspendSubCommand.java | 76 +++++++++++++++
.../org/apache/hadoop/ozone/admin/om/OMAdmin.java | 3 +-
20 files changed, 720 insertions(+), 8 deletions(-)
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 9a945dfea87..2287d904da3 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
@@ -278,6 +278,7 @@ public static boolean isReadOnly(
case GetQuotaRepairStatus:
case StartQuotaRepair:
case GetLifecycleConfiguration:
+ case GetLifecycleServiceStatus:
return true;
case CreateVolume:
case SetVolumeProperty:
@@ -341,6 +342,7 @@ public static boolean isReadOnly(
case DeleteObjectTagging:
case SetLifecycleConfiguration:
case DeleteLifecycleConfiguration:
+ case SetLifecycleServiceStatus:
case UnknownCommand:
return false;
case EchoRPC:
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 626bef65044..f8d686f53ad 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
@@ -66,6 +66,7 @@
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.CancelPrepareResponse;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.EchoRPCResponse;
+import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetLifecycleServiceStatusResponse;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OzoneAclInfo;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.PrepareStatusResponse;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.PrepareStatusResponse.PrepareStatus;
@@ -1226,4 +1227,32 @@ default void deleteLifecycleConfiguration(String
volumeName,
throw new UnsupportedOperationException("OzoneManager does not require " +
"this to be implemented, as write requests use a new approach.");
}
+
+ /**
+ * Gets the lifecycle service status.
+ * @return GetLifecycleServiceStatusResponse
+ * @throws IOException
+ */
+ default GetLifecycleServiceStatusResponse getLifecycleServiceStatus() throws
IOException {
+ throw new UnsupportedOperationException("OzoneManager does not require " +
+ "this to be implemented, as write requests use a new approach.");
+ }
+
+ /**
+ * Suspends the lifecycle service.
+ * @throws IOException
+ */
+ default void suspendLifecycleService() throws IOException {
+ throw new UnsupportedOperationException("OzoneManager does not require " +
+ "this to be implemented, as write requests use a new approach.");
+ }
+
+ /**
+ * Resumes the lifecycle service.
+ * @throws IOException
+ */
+ default void resumeLifecycleService() throws IOException {
+ throw new UnsupportedOperationException("OzoneManager does not require " +
+ "this to be implemented, as write requests use a new approach.");
+ }
}
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 3a2d41c0caf..94fbe0e0f06 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
@@ -141,6 +141,7 @@
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetKeyInfoResponse;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetLifecycleConfigurationRequest;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetLifecycleConfigurationResponse;
+import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetLifecycleServiceStatusResponse;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetObjectTaggingRequest;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetObjectTaggingResponse;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetS3SecretRequest;
@@ -2691,6 +2692,21 @@ public OmLifecycleConfiguration
getLifecycleConfiguration(String volumeName,
resp.getLifecycleConfiguration());
}
+ @Override
+ public GetLifecycleServiceStatusResponse getLifecycleServiceStatus() throws
IOException {
+ OzoneManagerProtocolProtos.GetLifecycleServiceStatusRequest
+ getLifecycleServiceStatusRequest =
+ OzoneManagerProtocolProtos.GetLifecycleServiceStatusRequest
+ .newBuilder().build();
+
+ OMRequest omRequest = createOMRequest(Type.GetLifecycleServiceStatus)
+ .setGetLifecycleServiceStatusRequest(getLifecycleServiceStatusRequest)
+ .build();
+
+ return handleError(submitRequest(omRequest))
+ .getGetLifecycleServiceStatusResponse();
+ }
+
@Override
public void setLifecycleConfiguration(
OmLifecycleConfiguration omLifecycleConfiguration) throws IOException {
@@ -2724,6 +2740,34 @@ public void deleteLifecycleConfiguration(String
volumeName, String bucketName)
handleError(submitRequest(omRequest));
}
+ @Override
+ public void suspendLifecycleService() throws IOException {
+ OzoneManagerProtocolProtos.SetLifecycleServiceStatusRequest
+ setLifecycleServiceStatusRequest =
+ OzoneManagerProtocolProtos.SetLifecycleServiceStatusRequest
+ .newBuilder().setSuspend(true).build();
+
+ OMRequest omRequest = createOMRequest(Type.SetLifecycleServiceStatus)
+ .setSetLifecycleServiceStatusRequest(setLifecycleServiceStatusRequest)
+ .build();
+
+ handleError(submitRequest(omRequest));
+ }
+
+ @Override
+ public void resumeLifecycleService() throws IOException {
+ OzoneManagerProtocolProtos.SetLifecycleServiceStatusRequest
+ setLifecycleServiceStatusRequest =
+ OzoneManagerProtocolProtos.SetLifecycleServiceStatusRequest
+ .newBuilder().setSuspend(false).build();
+
+ OMRequest omRequest = createOMRequest(Type.SetLifecycleServiceStatus)
+ .setSetLifecycleServiceStatusRequest(setLifecycleServiceStatusRequest)
+ .build();
+
+ handleError(submitRequest(omRequest));
+ }
+
private SafeMode toProtoBuf(SafeModeAction action) {
switch (action) {
case ENTER:
diff --git
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestOzoneShellHA.java
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestOzoneShellHA.java
index d7fbce84f1e..2dad2eef7ac 100644
---
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestOzoneShellHA.java
+++
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestOzoneShellHA.java
@@ -194,6 +194,7 @@ protected static void startCluster(OzoneConfiguration conf)
throws Exception {
conf.setInt(OMConfigKeys.OZONE_DIR_DELETING_SERVICE_INTERVAL, 10);
conf.setBoolean(OMConfigKeys.OZONE_OM_ENABLE_FILESYSTEM_PATHS, true);
conf.setInt(ScmConfigKeys.OZONE_SCM_CONTAINER_LIST_MAX_COUNT, 1);
+ conf.setBoolean(OMConfigKeys.OZONE_KEY_LIFECYCLE_SERVICE_ENABLED, true);
ozoneConfiguration = conf;
MiniOzoneHAClusterImpl.Builder builder =
MiniOzoneCluster.newHABuilder(conf);
builder.setOMServiceId(omServiceId)
@@ -1834,6 +1835,81 @@ public void testSetEncryptionKey() throws Exception {
assertEquals(newEncKey,
volume.getBucket("bucket0").getEncryptionKeyName());
}
+ @Test
+ public void testLifecycleStatus() throws UnsupportedEncodingException {
+ String[] args = new String[] {"om", "lifecycle", "status", "--service-id",
omServiceId};
+ execute(ozoneAdminShell, args);
+ String output = out.toString(DEFAULT_ENCODING);
+ assertThat(output).contains("IsEnabled:");
+ }
+
+ @Test
+ public void testLifecycleSuspendAndResume() throws Exception {
+ List<OzoneManager> ozoneManagers = cluster.getOzoneManagersList();
+ for (OzoneManager om : ozoneManagers) {
+ assertNotNull(om.getKeyManager().getKeyLifecycleService());
+ assertTrue(om.getLifecycleServiceStatus().getIsEnabled());
+ assertFalse(om.getLifecycleServiceStatus().getIsSuspended());
+ }
+
+ // Execute suspend command
+ String[] args = new String[] {"om", "lifecycle", "suspend",
"--service-id", omServiceId};
+ execute(ozoneAdminShell, args);
+ String output = out.toString(DEFAULT_ENCODING);
+ assertThat(output).contains("Lifecycle Service has been suspended");
+ out.reset();
+
+ // Wait for the suspend command to propagate through Ratis to all OMs
+ GenericTestUtils.waitFor(() -> {
+ for (OzoneManager om : ozoneManagers) {
+ assertNotNull(om.getKeyManager().getKeyLifecycleService());
+ if (!om.getLifecycleServiceStatus().getIsSuspended()) {
+ return false;
+ }
+ }
+ return true;
+ }, 100, 10000);
+
+ // Verify lifecycle service is suspended on all OMs
+ for (OzoneManager om : ozoneManagers) {
+ if (om.getKeyManager().getKeyLifecycleService() != null) {
+ assertTrue(om.getLifecycleServiceStatus().getIsSuspended(),
+ "Lifecycle service should be suspended on OM: " +
om.getOMNodeId());
+ // isEnabled should still be true (based on configuration)
+ assertTrue(om.getLifecycleServiceStatus().getIsEnabled(),
+ "Lifecycle service isEnabled should still be true on OM: " +
om.getOMNodeId());
+ }
+ }
+
+ // Execute resume command
+ args = new String[] {"om", "lifecycle", "resume", "--service-id",
omServiceId};
+ execute(ozoneAdminShell, args);
+ output = out.toString(DEFAULT_ENCODING);
+ assertThat(output).contains("Lifecycle Service has been resumed");
+ out.reset();
+
+ // Wait for the resume command to propagate through Ratis to all OMs
+ GenericTestUtils.waitFor(() -> {
+ for (OzoneManager om : ozoneManagers) {
+ assertNotNull(om.getKeyManager().getKeyLifecycleService());
+ if (om.getLifecycleServiceStatus().getIsSuspended()) {
+ return false;
+ }
+ }
+ return true;
+ }, 100, 10000);
+
+ // Verify lifecycle service is resumed on all OMs
+ for (OzoneManager om : ozoneManagers) {
+ if (om.getKeyManager().getKeyLifecycleService() != null) {
+ assertFalse(om.getLifecycleServiceStatus().getIsSuspended(),
+ "Lifecycle service should be resumed on OM: " + om.getOMNodeId());
+ assertTrue(om.getLifecycleServiceStatus().getIsEnabled(),
+ "Lifecycle service isEnabled should be true on OM: " +
om.getOMNodeId());
+ }
+ }
+ }
+
@Test
public void testCreateBucketWithECReplicationConfigWithoutReplicationParam()
{
getVolume("volume102");
diff --git
a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
index 137e44be877..0c0265db83f 100644
--- a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
+++ b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
@@ -159,6 +159,8 @@ enum Type {
SetLifecycleConfiguration = 150;
GetLifecycleConfiguration = 151;
DeleteLifecycleConfiguration = 152;
+ GetLifecycleServiceStatus = 153;
+ SetLifecycleServiceStatus = 154;
}
enum SafeMode {
@@ -310,6 +312,9 @@ message OMRequest {
optional SetLifecycleConfigurationRequest setLifecycleConfigurationRequest =
150;
optional GetLifecycleConfigurationRequest getLifecycleConfigurationRequest
= 151;
optional DeleteLifecycleConfigurationRequest
deleteLifecycleConfigurationRequest = 152;
+
+ optional GetLifecycleServiceStatusRequest getLifecycleServiceStatusRequest =
153;
+ optional SetLifecycleServiceStatusRequest setLifecycleServiceStatusRequest =
154;
}
message OMResponse {
@@ -447,6 +452,9 @@ message OMResponse {
optional SetLifecycleConfigurationResponse setLifecycleConfigurationResponse
= 150;
optional GetLifecycleConfigurationResponse
getLifecycleConfigurationResponse = 151;
optional DeleteLifecycleConfigurationResponse
deleteLifecycleConfigurationResponse = 152;
+
+ optional GetLifecycleServiceStatusResponse getLifecycleServiceStatusResponse
= 153;
+ optional SetLifecycleServiceStatusResponse setLifecycleServiceStatusResponse
= 154;
}
enum Status {
@@ -2439,3 +2447,20 @@ service OzoneManagerService {
rpc submitRequest(OMRequest)
returns(OMResponse);
}
+
+message GetLifecycleServiceStatusRequest {
+}
+
+message GetLifecycleServiceStatusResponse {
+ required bool isEnabled = 1;
+ optional bool isSuspended = 2;
+ repeated string runningBuckets = 3;
+}
+
+message SetLifecycleServiceStatusRequest {
+ required bool suspend = 1;
+}
+
+message SetLifecycleServiceStatusResponse {
+}
+
diff --git
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java
index 10cd0b21291..fa045294175 100644
---
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java
+++
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java
@@ -117,7 +117,9 @@ public enum OMAction implements AuditAction {
GET_LIFECYCLE_CONFIGURATION,
SET_LIFECYCLE_CONFIGURATION,
- DELETE_LIFECYCLE_CONFIGURATION;
+ DELETE_LIFECYCLE_CONFIGURATION,
+ GET_LIFECYCLE_SERVICE_STATUS,
+ SET_LIFECYCLE_SERVICE_STATUS;
@Override
public String getAction() {
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 b78368a441f..19838691c8d 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
@@ -53,6 +53,8 @@
import static
org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_DEFAULT_BUCKET_LAYOUT;
import static
org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_DEFAULT_BUCKET_LAYOUT_DEFAULT;
import static
org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_KEY_DELETING_LIMIT_PER_TASK;
+import static
org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_KEY_LIFECYCLE_SERVICE_ENABLED;
+import static
org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_KEY_LIFECYCLE_SERVICE_ENABLED_DEFAULT;
import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_ADDRESS_KEY;
import static
org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_EDEKCACHELOADER_INITIAL_DELAY_MS_DEFAULT;
import static
org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_EDEKCACHELOADER_INITIAL_DELAY_MS_KEY;
@@ -283,6 +285,7 @@
import org.apache.hadoop.ozone.om.s3.S3SecretCacheProvider;
import org.apache.hadoop.ozone.om.s3.S3SecretStoreProvider;
import org.apache.hadoop.ozone.om.service.CompactDBService;
+import org.apache.hadoop.ozone.om.service.KeyLifecycleService;
import org.apache.hadoop.ozone.om.service.OMRangerBGSyncService;
import org.apache.hadoop.ozone.om.service.QuotaRepairTask;
import org.apache.hadoop.ozone.om.snapshot.OmSnapshotUtils;
@@ -295,6 +298,7 @@
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.DBUpdatesRequest;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.EchoRPCResponse;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.ExtendedUserAccessIdInfo;
+import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetLifecycleServiceStatusResponse;
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.S3Authentication;
@@ -3159,6 +3163,18 @@ public OmLifecycleConfiguration
getLifecycleConfiguration(String volumeName,
}
}
+ @Override
+ public GetLifecycleServiceStatusResponse getLifecycleServiceStatus() {
+ KeyLifecycleService keyLifecycleService =
keyManager.getKeyLifecycleService();
+ if (keyLifecycleService == null) {
+ return GetLifecycleServiceStatusResponse.newBuilder()
+
.setIsEnabled(getConfiguration().getBoolean(OZONE_KEY_LIFECYCLE_SERVICE_ENABLED,
+ OZONE_KEY_LIFECYCLE_SERVICE_ENABLED_DEFAULT))
+ .build();
+ }
+ return keyLifecycleService.status();
+ }
+
private Map<String, String> buildAuditMap(String volume) {
Map<String, String> auditMap = new LinkedHashMap<>();
auditMap.put(OzoneConsts.VOLUME, volume);
diff --git
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/helpers/OMAuditLogger.java
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/helpers/OMAuditLogger.java
index 80c20f7af6d..7673663fa04 100644
---
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/helpers/OMAuditLogger.java
+++
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/helpers/OMAuditLogger.java
@@ -93,6 +93,7 @@ private static void init() {
CMD_AUDIT_ACTION_MAP.put(Type.GetObjectTagging,
OMAction.GET_OBJECT_TAGGING);
CMD_AUDIT_ACTION_MAP.put(Type.PutObjectTagging,
OMAction.PUT_OBJECT_TAGGING);
CMD_AUDIT_ACTION_MAP.put(Type.DeleteObjectTagging,
OMAction.DELETE_OBJECT_TAGGING);
+ CMD_AUDIT_ACTION_MAP.put(Type.SetLifecycleServiceStatus,
OMAction.SET_LIFECYCLE_SERVICE_STATUS);
}
private static OMAction getAction(OzoneManagerProtocolProtos.OMRequest
request) {
diff --git
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java
index 1f8f0e24a27..dcaf47b1754 100644
---
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java
+++
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java
@@ -68,6 +68,7 @@
import org.apache.hadoop.ozone.om.request.key.acl.prefix.OMPrefixSetAclRequest;
import
org.apache.hadoop.ozone.om.request.lifecycle.OMLifecycleConfigurationDeleteRequest;
import
org.apache.hadoop.ozone.om.request.lifecycle.OMLifecycleConfigurationSetRequest;
+import
org.apache.hadoop.ozone.om.request.lifecycle.OMLifecycleSetServiceStatusRequest;
import
org.apache.hadoop.ozone.om.request.s3.multipart.S3ExpiredMultipartUploadsAbortRequest;
import org.apache.hadoop.ozone.om.request.s3.security.OMSetSecretRequest;
import org.apache.hadoop.ozone.om.request.s3.security.S3GetSecretRequest;
@@ -351,6 +352,8 @@ public static OMClientRequest createClientRequest(OMRequest
omRequest,
return new OMLifecycleConfigurationSetRequest(omRequest);
case DeleteLifecycleConfiguration:
return new OMLifecycleConfigurationDeleteRequest(omRequest);
+ case SetLifecycleServiceStatus:
+ return new OMLifecycleSetServiceStatusRequest(omRequest);
default:
throw new OMException("Unrecognized write command type request "
+ cmdType, OMException.ResultCodes.INVALID_REQUEST);
diff --git
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/lifecycle/OMLifecycleSetServiceStatusRequest.java
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/lifecycle/OMLifecycleSetServiceStatusRequest.java
new file mode 100644
index 00000000000..be9e92b3c1d
--- /dev/null
+++
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/lifecycle/OMLifecycleSetServiceStatusRequest.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.request.lifecycle;
+
+import java.io.IOException;
+import java.util.HashMap;
+import org.apache.hadoop.ozone.audit.AuditLogger;
+import org.apache.hadoop.ozone.audit.OMAction;
+import org.apache.hadoop.ozone.om.OzoneManager;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.execution.flowcontrol.ExecutionContext;
+import org.apache.hadoop.ozone.om.request.OMClientRequest;
+import org.apache.hadoop.ozone.om.request.util.OmResponseUtil;
+import org.apache.hadoop.ozone.om.response.OMClientResponse;
+import
org.apache.hadoop.ozone.om.response.lifecycle.OMLifecycleSetServiceStatusResponse;
+import org.apache.hadoop.ozone.om.service.KeyLifecycleService;
+import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
+import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse;
+import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SetLifecycleServiceStatusResponse;
+import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.UserInfo;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Handles SetLifecycleServiceStatus Request.
+ * This request suspends or resumes the KeyLifecycleService.
+ */
+public class OMLifecycleSetServiceStatusRequest extends OMClientRequest {
+ private static final Logger LOG =
+ LoggerFactory.getLogger(OMLifecycleSetServiceStatusRequest.class);
+
+ public OMLifecycleSetServiceStatusRequest(OMRequest omRequest) {
+ super(omRequest);
+ }
+
+ @Override
+ public OMClientResponse validateAndUpdateCache(OzoneManager ozoneManager,
ExecutionContext context) {
+ OMResponse.Builder omResponse =
OmResponseUtil.getOMResponseBuilder(getOmRequest());
+ AuditLogger auditLogger = ozoneManager.getAuditLogger();
+ UserInfo userInfo = getOmRequest().getUserInfo();
+ HashMap<String, String> auditMap = new HashMap<>();
+ IOException exception = null;
+ OMClientResponse omClientResponse;
+ boolean suspend =
getOmRequest().getSetLifecycleServiceStatusRequest().getSuspend();
+ auditMap.put("suspend", String.valueOf(suspend));
+
+ try {
+ if (ozoneManager.getAclsEnabled()) {
+ UserGroupInformation ugi = createUGIForApi();
+ if (!ozoneManager.isAdmin(ugi)) {
+ throw new OMException("Access denied for user " + ugi + ". "
+ + "Superuser privilege is required to " + (suspend ? "suspend" :
"resume") + " Lifecycle Service.",
+ OMException.ResultCodes.ACCESS_DENIED);
+ }
+ }
+
+ KeyLifecycleService keyLifecycleService =
ozoneManager.getKeyManager().getKeyLifecycleService();
+ if (keyLifecycleService != null) {
+ if (suspend) {
+ keyLifecycleService.suspend();
+ LOG.info("KeyLifecycleService has been suspended by user: {}",
+ userInfo != null ? userInfo.getUserName() : "unknown");
+ } else {
+ keyLifecycleService.resume();
+ LOG.info("KeyLifecycleService resume called by user: {}",
+ userInfo != null ? userInfo.getUserName() : "unknown");
+ }
+ } else {
+ LOG.warn("KeyLifecycleService is not available");
+ }
+
+ omResponse.setSetLifecycleServiceStatusResponse(
+ SetLifecycleServiceStatusResponse.newBuilder().build());
+ omClientResponse = new
OMLifecycleSetServiceStatusResponse(omResponse.build());
+ } catch (IOException ex) {
+ exception = ex;
+ LOG.error("Failed to " + (suspend ? "suspend" : "resume") + "
KeyLifecycleService", ex);
+ omClientResponse = new OMLifecycleSetServiceStatusResponse(
+ createErrorOMResponse(omResponse, ex));
+ }
+
+ markForAudit(auditLogger,
buildAuditMessage(OMAction.SET_LIFECYCLE_SERVICE_STATUS,
+ auditMap, exception, userInfo));
+ return omClientResponse;
+ }
+}
+
diff --git
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/lifecycle/OMLifecycleSetServiceStatusResponse.java
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/lifecycle/OMLifecycleSetServiceStatusResponse.java
new file mode 100644
index 00000000000..59f8b0963ff
--- /dev/null
+++
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/lifecycle/OMLifecycleSetServiceStatusResponse.java
@@ -0,0 +1,46 @@
+/*
+ * 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.response.lifecycle;
+
+import java.io.IOException;
+import org.apache.hadoop.hdds.utils.db.BatchOperation;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
+import org.apache.hadoop.ozone.om.response.CleanupTableInfo;
+import org.apache.hadoop.ozone.om.response.OMClientResponse;
+import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse;
+
+/**
+ * Response for SetLifecycleServiceStatus request.
+ * This response does not modify any database tables.
+ */
+@CleanupTableInfo
+public class OMLifecycleSetServiceStatusResponse extends OMClientResponse {
+
+ public OMLifecycleSetServiceStatusResponse(OMResponse omResponse) {
+ super(omResponse);
+ }
+
+ @Override
+ protected void addToDBBatch(OMMetadataManager omMetadataManager,
+ BatchOperation batchOperation)
+ throws IOException {
+ // No database update required for setting the lifecycle service state.
+ // The service state is maintained in memory only.
+ }
+}
+
diff --git
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/KeyLifecycleService.java
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/KeyLifecycleService.java
index 6b87cd124b5..f115ef181e2 100644
---
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/KeyLifecycleService.java
+++
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/KeyLifecycleService.java
@@ -43,6 +43,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -84,6 +85,7 @@
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.DeleteKeyArgs;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.DeleteKeyError;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.DeleteKeysRequest;
+import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetLifecycleServiceStatusResponse;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.KeyArgs;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.RenameKeyRequest;
@@ -106,8 +108,8 @@ public class KeyLifecycleService extends BackgroundService {
private int listMaxSize;
private long cachedDirMaxCount;
private final AtomicBoolean suspended;
+ private final AtomicBoolean isServiceEnabled;
private KeyLifecycleServiceMetrics metrics;
- private boolean isServiceEnabled;
// A set of bucket name that have LifecycleActionTask scheduled
private final ConcurrentHashMap<String, LifecycleActionTask> inFlight;
private OMMetadataManager omMetadataManager;
@@ -133,8 +135,8 @@ public KeyLifecycleService(OzoneManager ozoneManager,
OZONE_KEY_LIFECYCLE_SERVICE_DELETE_CACHED_DIRECTORY_MAX_COUNT_DEFAULT);
this.suspended = new AtomicBoolean(false);
this.metrics = KeyLifecycleServiceMetrics.create();
- this.isServiceEnabled =
conf.getBoolean(OZONE_KEY_LIFECYCLE_SERVICE_ENABLED,
- OZONE_KEY_LIFECYCLE_SERVICE_ENABLED_DEFAULT);
+ this.isServiceEnabled = new
AtomicBoolean(conf.getBoolean(OZONE_KEY_LIFECYCLE_SERVICE_ENABLED,
+ OZONE_KEY_LIFECYCLE_SERVICE_ENABLED_DEFAULT));
this.inFlight = new ConcurrentHashMap();
this.omMetadataManager = ozoneManager.getMetadataManager();
int limit = (int) conf.getStorageSize(
@@ -193,7 +195,7 @@ private boolean shouldRun() {
// OzoneManager can be null for testing
return true;
}
- return isServiceEnabled && !suspended.get() &&
getOzoneManager().isLeaderReady();
+ return isServiceEnabled.get() && !suspended.get() &&
getOzoneManager().isLeaderReady();
}
public KeyLifecycleServiceMetrics getMetrics() {
@@ -207,7 +209,6 @@ public OzoneManager getOzoneManager() {
/**
* Suspend the service.
*/
- @VisibleForTesting
public void suspend() {
suspended.set(true);
}
@@ -215,17 +216,33 @@ public void suspend() {
/**
* Resume the service if suspended.
*/
- @VisibleForTesting
public void resume() {
suspended.set(false);
}
+ public boolean isSuspended() {
+ return suspended.get();
+ }
+
@Override
public void shutdown() {
super.shutdown();
KeyLifecycleServiceMetrics.unregister();
}
+ /**
+ * Build a GetLifecycleServiceStatusResponse instance.
+ * @return GetLifecycleServiceStatusResponse instance
+ */
+ public GetLifecycleServiceStatusResponse status() {
+ Set<String> runningBuckets = new HashSet<>(inFlight.keySet());
+ return GetLifecycleServiceStatusResponse.newBuilder()
+ .setIsEnabled(isServiceEnabled.get())
+ .setIsSuspended(suspended.get())
+ .addAllRunningBuckets(runningBuckets)
+ .build();
+ }
+
/**
* A lifecycle action task for one specific bucket, scanning OM DB and
evaluating if any existing
* object/key qualified for expiration according to bucket's lifecycle
configuration, and sending
@@ -471,6 +488,13 @@ private void evaluateKeyAndDirTable(OmBucketInfo bucket,
long volumeObjId, Table
HashSet<Long> deletedDirSet = new HashSet<>();
while (!stack.isEmpty()) {
+ if (!shouldRun()) {
+ LOG.info("LifecycleActionTask for bucket {} stopping. " +
+ "Service enabled: {}, suspended: {}, leader ready: {}",
+ bucketName, isServiceEnabled.get(), suspended.get(),
+ getOzoneManager() != null ? getOzoneManager().isLeaderReady() :
"N/A");
+ return;
+ }
PendingEvaluateDirectory item = stack.pop();
OmDirectoryInfo currentDir = item.getDirectoryInfo();
String currentDirPath = item.getDirPath();
@@ -699,6 +723,11 @@ private void evaluateBucket(OmBucketInfo bucketInfo,
try (TableIterator<String, ? extends Table.KeyValue<String, OmKeyInfo>>
keyTblItr =
keyTable.iterator(omMetadataManager.getBucketKey(volumeName,
bucketName))) {
while (keyTblItr.hasNext()) {
+ if (!shouldRun()) {
+ LOG.info("KeyLifecycleService is suspended or disabled. " +
+ "Stopping LifecycleActionTask for bucket {}.", bucketName);
+ return;
+ }
Table.KeyValue<String, OmKeyInfo> keyValue = keyTblItr.next();
OmKeyInfo key = keyValue.getValue();
numKeyIterated++;
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 debbbebf874..eaa578f6136 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
@@ -116,6 +116,7 @@
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetKeyInfoResponse;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetLifecycleConfigurationRequest;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetLifecycleConfigurationResponse;
+import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetLifecycleServiceStatusResponse;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetObjectTaggingRequest;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetObjectTaggingResponse;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetS3VolumeContextResponse;
@@ -402,6 +403,12 @@ public OMResponse handleReadRequest(OMRequest request) {
responseBuilder.setGetLifecycleConfigurationResponse(
getLifecycleConfigurationResponse);
break;
+ case GetLifecycleServiceStatus:
+ GetLifecycleServiceStatusResponse getLifecycleServiceStatusResponse =
+ impl.getLifecycleServiceStatus();
+ responseBuilder.setGetLifecycleServiceStatusResponse(
+ getLifecycleServiceStatusResponse);
+ break;
default:
responseBuilder.setSuccess(false);
responseBuilder.setMessage("Unrecognized Command Type: " + cmdType);
diff --git
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/response/TestCleanupTableInfo.java
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/response/TestCleanupTableInfo.java
index 7def54ae5e8..62bdacb8c4a 100644
---
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/response/TestCleanupTableInfo.java
+++
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/response/TestCleanupTableInfo.java
@@ -60,6 +60,7 @@
import org.apache.hadoop.ozone.om.response.file.OMFileCreateResponse;
import org.apache.hadoop.ozone.om.response.key.OMKeyCreateResponse;
import org.apache.hadoop.ozone.om.response.key.OmKeyResponse;
+import
org.apache.hadoop.ozone.om.response.lifecycle.OMLifecycleSetServiceStatusResponse;
import org.apache.hadoop.ozone.om.response.util.OMEchoRPCWriteResponse;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.CreateFileRequest;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.CreateKeyRequest;
@@ -135,6 +136,7 @@ public void checkAnnotationAndTableName() {
// OMEchoRPCWriteResponse does not need CleanupTable.
subTypes.remove(OMEchoRPCWriteResponse.class);
subTypes.remove(DummyOMClientResponse.class);
+ subTypes.remove(OMLifecycleSetServiceStatusResponse.class);
subTypes.forEach(aClass -> {
assertTrue(aClass.isAnnotationPresent(CleanupTableInfo.class),
aClass + " does not have annotation of" +
diff --git
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestKeyLifecycleService.java
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestKeyLifecycleService.java
index 8d9d457ef61..7b1b0e57c7c 100644
---
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestKeyLifecycleService.java
+++
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestKeyLifecycleService.java
@@ -101,6 +101,7 @@
import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol;
import org.apache.hadoop.ozone.om.request.OMRequestTestUtils;
import org.apache.hadoop.ozone.om.request.key.OMKeysDeleteRequest;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.LifecycleConfiguration;
import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
import org.apache.hadoop.ozone.security.acl.OzoneObj;
@@ -114,6 +115,7 @@
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.io.TempDir;
@@ -1537,6 +1539,45 @@ void testMultipleDirectoriesMatched(String keyPrefix1,
String keyPrefix2, String
}
deleteLifecyclePolicy(volumeName, bucketName);
}
+
+ @Test
+ void testGetLifecycleServiceStatus() throws Exception {
+ final String volumeName = getTestName();
+ final String bucketName = uniqueObjectName("bucket");
+ String prefix = "key";
+
+ //Service should be enabled but not running
+ OzoneManagerProtocolProtos.GetLifecycleServiceStatusResponse status =
+ om.getLifecycleServiceStatus();
+ assertTrue(status.getIsEnabled());
+ assertEquals(0, status.getRunningBucketsCount());
+
+ // Create and inject for test
+ createKeys(volumeName, bucketName, FILE_SYSTEM_OPTIMIZED, KEY_COUNT, 1,
prefix, null);
+ ZonedDateTime date =
ZonedDateTime.now(ZoneOffset.UTC).plusSeconds(EXPIRE_SECONDS);
+ KeyLifecycleService.setInjectors(
+ Arrays.asList(new FaultInjectorImpl(), new FaultInjectorImpl()));
+ createLifecyclePolicy(volumeName, bucketName, FILE_SYSTEM_OPTIMIZED,
prefix, null, date.toString(), true);
+ Thread.sleep(SERVICE_INTERVAL + 100);
+
+ // Verify service is running and processing the bucket
+ status = om.getLifecycleServiceStatus();
+ assertTrue(status.getIsEnabled());
+ assertEquals(1, status.getRunningBucketsCount());
+ assertTrue(status.getRunningBucketsList().contains("/" + volumeName +
"/" + bucketName));
+
+ KeyLifecycleService.getInjector(0).resume();
+ KeyLifecycleService.getInjector(1).resume();
+ GenericTestUtils.waitFor(() ->
om.getLifecycleServiceStatus().getRunningBucketsCount() == 0,
+ WAIT_CHECK_INTERVAL, 10000);
+
+ // Verify service completed and is no longer running
+ status = om.getLifecycleServiceStatus();
+ assertTrue(status.getIsEnabled());
+ assertEquals(0, status.getRunningBucketsCount());
+
+ deleteLifecyclePolicy(volumeName, bucketName);
+ }
}
/**
diff --git
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/LifecycleResumeSubCommand.java
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/LifecycleResumeSubCommand.java
new file mode 100644
index 00000000000..69f6c66a90d
--- /dev/null
+++
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/LifecycleResumeSubCommand.java
@@ -0,0 +1,73 @@
+/*
+ * 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.admin.om;
+
+import java.io.PrintStream;
+import java.util.concurrent.Callable;
+import org.apache.hadoop.hdds.cli.HddsVersionProvider;
+import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+
+/**
+ * Handler of ozone admin om lifecycle resume command.
+ */
+@Command(
+ name = "resume",
+ description = "Resume Lifecycle Service that was previously suspended",
+ mixinStandardHelpOptions = true,
+ versionProvider = HddsVersionProvider.class)
+public class LifecycleResumeSubCommand implements Callable<Void> {
+
+ @CommandLine.ParentCommand
+ private LifecycleSubCommand parent;
+
+ @CommandLine.Option(
+ names = {"-id", "--service-id"},
+ description = "Ozone Manager Service ID"
+ )
+ private String omServiceId;
+
+ @CommandLine.Option(
+ names = {"-host", "--service-host"},
+ description = "Ozone Manager Host"
+ )
+ private String omHost;
+
+ @Override
+ public Void call() throws Exception {
+ try (OzoneManagerProtocol ozoneManagerClient =
+ parent.getParent().createOmClient(omServiceId, omHost, false)) {
+ ozoneManagerClient.resumeLifecycleService();
+ output();
+ }
+ return null;
+ }
+
+ protected void output() {
+ PrintStream out = out();
+ out.println("========================================");
+ out.println("Lifecycle Service has been resumed.");
+ out.println("========================================");
+ }
+
+ protected PrintStream out() {
+ return System.out;
+ }
+}
+
diff --git
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/LifecycleStatusSubCommand.java
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/LifecycleStatusSubCommand.java
new file mode 100644
index 00000000000..a443471391a
--- /dev/null
+++
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/LifecycleStatusSubCommand.java
@@ -0,0 +1,89 @@
+/*
+ * 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.admin.om;
+
+import java.io.PrintStream;
+import java.util.concurrent.Callable;
+import org.apache.hadoop.hdds.cli.HddsVersionProvider;
+import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol;
+import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetLifecycleServiceStatusResponse;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+
+/**
+ * Handler of ozone admin om lifecycle status command.
+ */
+@Command(
+ name = "status",
+ description = "Check Lifecycle Service status",
+ mixinStandardHelpOptions = true,
+ versionProvider = HddsVersionProvider.class)
+public class LifecycleStatusSubCommand implements Callable<Void> {
+
+ @CommandLine.ParentCommand
+ private LifecycleSubCommand parent;
+
+ @CommandLine.Option(
+ names = {"-id", "--service-id"},
+ description = "Ozone Manager Service ID"
+ )
+ private String omServiceId;
+
+ @CommandLine.Option(
+ names = {"-host", "--service-host"},
+ description = "Ozone Manager Host"
+ )
+ private String omHost;
+
+ @Override
+ public Void call() throws Exception {
+ try (OzoneManagerProtocol ozoneManagerClient =
+ parent.getParent().createOmClient(omServiceId, omHost, false)) {
+ GetLifecycleServiceStatusResponse lifecycleServiceStatus =
+ ozoneManagerClient.getLifecycleServiceStatus();
+ output(lifecycleServiceStatus);
+ }
+ return null;
+ }
+
+ protected void output(GetLifecycleServiceStatusResponse status) {
+ PrintStream out = out();
+ out.println("========================================");
+ out.println(" Lifecycle Service Status");
+ out.println("========================================");
+ out.printf("IsEnabled: %s%n", status.getIsEnabled());
+ if (status.getIsEnabled() && status.hasIsSuspended()) {
+ out.printf("IsSuspended: %s%n", status.getIsSuspended());
+ }
+
+ if (status.getRunningBucketsCount() > 0) {
+ out.println("Running Buckets:");
+ for (String bucket : status.getRunningBucketsList()) {
+ out.printf(" - %s%n", bucket);
+ }
+ } else {
+ out.println("No buckets are currently being processed.");
+ }
+ out.println("========================================");
+ }
+
+ protected PrintStream out() {
+ return System.out;
+ }
+}
+
diff --git
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/LifecycleSubCommand.java
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/LifecycleSubCommand.java
new file mode 100644
index 00000000000..7517618e84b
--- /dev/null
+++
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/LifecycleSubCommand.java
@@ -0,0 +1,47 @@
+/*
+ * 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.admin.om;
+
+import org.apache.hadoop.hdds.cli.AdminSubcommand;
+import org.apache.hadoop.hdds.cli.HddsVersionProvider;
+import picocli.CommandLine;
+
+/**
+ * Subcommand to admin operations related to Lifecycle Service.
+ */
[email protected](
+ name = "lifecycle",
+ description = "Ozone Manager Lifecycle Service specific admin operations",
+ mixinStandardHelpOptions = true,
+ versionProvider = HddsVersionProvider.class,
+ subcommands = {
+ LifecycleStatusSubCommand.class,
+ LifecycleSuspendSubCommand.class,
+ LifecycleResumeSubCommand.class,
+ })
+public class LifecycleSubCommand implements AdminSubcommand {
+
+ @CommandLine.ParentCommand
+ private OMAdmin parent;
+
+ public OMAdmin getParent() {
+ return parent;
+ }
+
+}
+
diff --git
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/LifecycleSuspendSubCommand.java
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/LifecycleSuspendSubCommand.java
new file mode 100644
index 00000000000..8a420c3d41b
--- /dev/null
+++
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/LifecycleSuspendSubCommand.java
@@ -0,0 +1,76 @@
+/*
+ * 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.admin.om;
+
+import java.io.PrintStream;
+import java.util.concurrent.Callable;
+import org.apache.hadoop.hdds.cli.HddsVersionProvider;
+import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+
+/**
+ * Handler of ozone admin om lifecycle suspend command.
+ */
+@Command(
+ name = "suspend",
+ description = "Suspend Lifecycle Service. Use 'resume' command to resume
it, " +
+ "or it will be re-enabled after OM restarts based on the
configuration",
+ mixinStandardHelpOptions = true,
+ versionProvider = HddsVersionProvider.class)
+public class LifecycleSuspendSubCommand implements Callable<Void> {
+
+ @CommandLine.ParentCommand
+ private LifecycleSubCommand parent;
+
+ @CommandLine.Option(
+ names = {"-id", "--service-id"},
+ description = "Ozone Manager Service ID"
+ )
+ private String omServiceId;
+
+ @CommandLine.Option(
+ names = {"-host", "--service-host"},
+ description = "Ozone Manager Host"
+ )
+ private String omHost;
+
+ @Override
+ public Void call() throws Exception {
+ try (OzoneManagerProtocol ozoneManagerClient =
+ parent.getParent().createOmClient(omServiceId, omHost, false)) {
+ ozoneManagerClient.suspendLifecycleService();
+ output();
+ }
+ return null;
+ }
+
+ protected void output() {
+ PrintStream out = out();
+ out.println("========================================");
+ out.println("Lifecycle Service has been suspended.");
+ out.println("Use 'ozone admin om lifecycle resume' to resume it,");
+ out.println("or it will be re-enabled after OM restarts based on the
configuration.");
+ out.println("========================================");
+ }
+
+ protected PrintStream out() {
+ return System.out;
+ }
+}
+
diff --git
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/OMAdmin.java
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/OMAdmin.java
index d536b81be14..1ba095390d3 100644
---
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/OMAdmin.java
+++
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/om/OMAdmin.java
@@ -59,7 +59,8 @@
UpdateRangerSubcommand.class,
TransferOmLeaderSubCommand.class,
FetchKeySubCommand.class,
- LeaseSubCommand.class
+ LeaseSubCommand.class,
+ LifecycleSubCommand.class
})
@MetaInfServices(AdminSubcommand.class)
public class OMAdmin implements AdminSubcommand {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]