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 9a945dfea87e..2287d904da31 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 626bef650446..f8d686f53ada 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 3a2d41c0caf9..94fbe0e0f060 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 d7fbce84f1ee..2dad2eef7aca 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 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 ba35dab8470f..9d28133f1597 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 { @@ -2432,3 +2440,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 10cd0b212916..fa0452941750 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 b78368a441fd..19838691c8d6 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 buildAuditMap(String volume) { Map 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 80c20f7af6dc..7673663fa044 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 1f8f0e24a278..dcaf47b17542 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 000000000000..be9e92b3c1d3 --- /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 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 000000000000..59f8b0963ff9 --- /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 680ba2bdbb0c..c77b48616067 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; @@ -105,8 +107,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 inFlight; private OMMetadataManager omMetadataManager; @@ -132,8 +134,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( @@ -192,7 +194,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() { @@ -206,7 +208,6 @@ public OzoneManager getOzoneManager() { /** * Suspend the service. */ - @VisibleForTesting public void suspend() { suspended.set(true); } @@ -214,17 +215,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 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 @@ -470,6 +487,13 @@ private void evaluateKeyAndDirTable(OmBucketInfo bucket, long volumeObjId, Table HashSet 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(); @@ -698,6 +722,11 @@ private void evaluateBucket(OmBucketInfo bucketInfo, try (TableIterator> 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 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 debbbebf874c..eaa578f61362 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 7def54ae5e8b..62bdacb8c4a4 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 8d9d457ef61f..7b1b0e57c7ca 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 000000000000..69f6c66a90da --- /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 { + + @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 000000000000..a443471391ac --- /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 { + + @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 000000000000..7517618e84bd --- /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. + */ +@CommandLine.Command( + 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 000000000000..8a420c3d41b6 --- /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 { + + @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 d536b81be140..1ba095390d3e 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 {