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 27015d34a35a..8fa8921cc9a9 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 @@ -279,6 +279,8 @@ public static boolean isReadOnly( case PrintCompactionLogDag: case GetSnapshotInfo: case GetServerDefaults: + case GetQuotaRepairStatus: + case StartQuotaRepair: return true; case CreateVolume: case SetVolumeProperty: 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 0f01761b17b1..37481b00ea28 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 @@ -1153,4 +1153,17 @@ boolean setSafeMode(SafeModeAction action, boolean isChecked) * @throws IOException */ OzoneFsServerDefaults getServerDefaults() throws IOException; + + /** + * Get status of last triggered quota repair in OM. + * @return String + * @throws IOException + */ + String getQuotaRepairStatus() throws IOException; + + /** + * start quota repair in OM. + * @throws IOException + */ + void startQuotaRepair(List buckets) throws IOException; } 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 d3e39550dfbc..bdd1428b16dc 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 @@ -2579,6 +2579,30 @@ public OzoneFsServerDefaults getServerDefaults() serverDefaultsResponse.getServerDefaults()); } + @Override + public String getQuotaRepairStatus() throws IOException { + OzoneManagerProtocolProtos.GetQuotaRepairStatusRequest quotaRepairStatusRequest = + OzoneManagerProtocolProtos.GetQuotaRepairStatusRequest.newBuilder() + .build(); + + OMRequest omRequest = createOMRequest(Type.GetQuotaRepairStatus) + .setGetQuotaRepairStatusRequest(quotaRepairStatusRequest).build(); + + OzoneManagerProtocolProtos.GetQuotaRepairStatusResponse quotaRepairStatusResponse + = handleError(submitRequest(omRequest)).getGetQuotaRepairStatusResponse(); + return quotaRepairStatusResponse.getStatus(); + } + + @Override + public void startQuotaRepair(List buckets) throws IOException { + OzoneManagerProtocolProtos.StartQuotaRepairRequest startQuotaRepairRequest = + OzoneManagerProtocolProtos.StartQuotaRepairRequest.newBuilder() + .build(); + OMRequest omRequest = createOMRequest(Type.StartQuotaRepair) + .setStartQuotaRepairRequest(startQuotaRepairRequest).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/TestOzoneRepairShell.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestOzoneRepairShell.java index 328fc1ddd8c2..9216c909ee4b 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestOzoneRepairShell.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestOzoneRepairShell.java @@ -22,8 +22,13 @@ import org.apache.hadoop.ozone.debug.DBScanner; import org.apache.hadoop.ozone.debug.RDBParser; import org.apache.hadoop.ozone.om.OMStorage; +import org.apache.hadoop.ozone.repair.OzoneRepair; import org.apache.hadoop.ozone.repair.RDBRepair; import org.apache.hadoop.ozone.repair.TransactionInfoRepair; +import org.apache.hadoop.ozone.repair.quota.QuotaRepair; +import org.apache.hadoop.ozone.repair.quota.QuotaStatus; +import org.apache.hadoop.ozone.repair.quota.QuotaTrigger; +import org.apache.ozone.test.GenericTestUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -38,6 +43,7 @@ import java.util.regex.Pattern; import static org.apache.hadoop.ozone.OzoneConsts.OM_DB_NAME; +import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_ADDRESS_KEY; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static java.nio.charset.StandardCharsets.UTF_8; @@ -130,4 +136,28 @@ private String[] parseScanOutput(String output) throws IOException { throw new IllegalStateException("Failed to scan and find raft's highest term and index from TransactionInfo table"); } + @Test + public void testQuotaRepair() throws Exception { + CommandLine cmd = new CommandLine(new OzoneRepair()).addSubcommand(new CommandLine(new QuotaRepair()) + .addSubcommand(new QuotaStatus()).addSubcommand(new QuotaTrigger())); + + String[] args = new String[] {"quota", "status", "--service-host", conf.get(OZONE_OM_ADDRESS_KEY)}; + int exitCode = cmd.execute(args); + assertEquals(0, exitCode); + args = new String[] {"quota", "start", "--service-host", conf.get(OZONE_OM_ADDRESS_KEY)}; + exitCode = cmd.execute(args); + assertEquals(0, exitCode); + GenericTestUtils.waitFor(() -> { + out.reset(); + // verify quota trigger is completed having non-zero lastRunFinishedTime + String[] targs = new String[]{"quota", "status", "--service-host", conf.get(OZONE_OM_ADDRESS_KEY)}; + cmd.execute(targs); + try { + return !out.toString(DEFAULT_ENCODING).contains("\"lastRunFinishedTime\":\"\""); + } catch (Exception ex) { + // do nothing + } + return false; + }, 1000, 10000); + } } diff --git a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto index eeddc5500527..e79797993c13 100644 --- a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto +++ b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto @@ -151,6 +151,8 @@ enum Type { ListOpenFiles = 132; QuotaRepair = 133; GetServerDefaults = 134; + GetQuotaRepairStatus = 135; + StartQuotaRepair = 136; } enum SafeMode { @@ -291,6 +293,8 @@ message OMRequest { optional ListOpenFilesRequest ListOpenFilesRequest = 130; optional QuotaRepairRequest QuotaRepairRequest = 131; optional ServerDefaultsRequest ServerDefaultsRequest = 132; + optional GetQuotaRepairStatusRequest GetQuotaRepairStatusRequest = 133; + optional StartQuotaRepairRequest StartQuotaRepairRequest = 134; } message OMResponse { @@ -419,6 +423,8 @@ message OMResponse { optional ListOpenFilesResponse ListOpenFilesResponse = 133; optional QuotaRepairResponse QuotaRepairResponse = 134; optional ServerDefaultsResponse ServerDefaultsResponse = 135; + optional GetQuotaRepairStatusResponse GetQuotaRepairStatusResponse = 136; + optional StartQuotaRepairResponse StartQuotaRepairResponse = 137; } enum Status { @@ -2226,6 +2232,17 @@ message ServerDefaultsResponse { required FsServerDefaultsProto serverDefaults = 1; } +message GetQuotaRepairStatusRequest { +} +message GetQuotaRepairStatusResponse { + optional string status = 1; +} +message StartQuotaRepairRequest { + repeated string buckets = 1; +} +message StartQuotaRepairResponse { +} + message OMLockDetailsProto { optional bool isLockAcquired = 1; optional uint64 waitLockNanos = 2; 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 602158bb5c33..b6903ca9e91f 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 @@ -106,6 +106,7 @@ import org.apache.hadoop.ozone.om.s3.S3SecretCacheProvider; import org.apache.hadoop.ozone.om.s3.S3SecretStoreProvider; import org.apache.hadoop.ozone.om.service.OMRangerBGSyncService; +import org.apache.hadoop.ozone.om.service.QuotaRepairTask; import org.apache.hadoop.ozone.om.snapshot.OmSnapshotUtils; import org.apache.hadoop.ozone.om.snapshot.ReferenceCounted; import org.apache.hadoop.ozone.om.upgrade.OMLayoutFeature; @@ -4751,6 +4752,18 @@ public OzoneFsServerDefaults getServerDefaults() { return serverDefaults; } + @Override + public String getQuotaRepairStatus() throws IOException { + checkAdminUserPrivilege("quota repair status"); + return QuotaRepairTask.getStatus(); + } + + @Override + public void startQuotaRepair(List buckets) throws IOException { + checkAdminUserPrivilege("start quota repair"); + new QuotaRepairTask(this).repair(buckets); + } + /** * Write down Layout version of a finalized feature to DB on finalization. * @param lvm OMLayoutVersionManager diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/QuotaRepairTask.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/QuotaRepairTask.java index b3e64c98c5dd..c043a6a72f24 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/QuotaRepairTask.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/QuotaRepairTask.java @@ -26,6 +26,7 @@ import java.io.UncheckedIOException; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -48,6 +49,7 @@ import org.apache.hadoop.ozone.om.OMMetadataManager; import org.apache.hadoop.ozone.om.OmMetadataManagerImpl; import org.apache.hadoop.ozone.om.OzoneManager; +import org.apache.hadoop.ozone.om.exceptions.OMException; import org.apache.hadoop.ozone.om.helpers.BucketLayout; import org.apache.hadoop.ozone.om.helpers.OMRatisHelper; import org.apache.hadoop.ozone.om.helpers.OmBucketInfo; @@ -74,27 +76,31 @@ public class QuotaRepairTask { private static final int TASK_THREAD_CNT = 3; private static final AtomicBoolean IN_PROGRESS = new AtomicBoolean(false); private static final RepairStatus REPAIR_STATUS = new RepairStatus(); + private static final AtomicLong RUN_CNT = new AtomicLong(0); private final OzoneManager om; - private final AtomicLong runCount = new AtomicLong(0); private ExecutorService executor; public QuotaRepairTask(OzoneManager ozoneManager) { this.om = ozoneManager; } - public CompletableFuture repair() throws Exception { + public CompletableFuture repair() throws IOException { + return repair(Collections.emptyList()); + } + + public CompletableFuture repair(List buckets) throws IOException { // lock in progress operation and reject any other if (!IN_PROGRESS.compareAndSet(false, true)) { LOG.info("quota repair task already running"); - return CompletableFuture.supplyAsync(() -> false); + throw new OMException("Quota repair is already running", OMException.ResultCodes.QUOTA_ERROR); } - REPAIR_STATUS.reset(runCount.get() + 1); - return CompletableFuture.supplyAsync(() -> repairTask()); + REPAIR_STATUS.reset(RUN_CNT.get() + 1); + return CompletableFuture.supplyAsync(() -> repairTask(buckets)); } public static String getStatus() { return REPAIR_STATUS.toString(); } - private boolean repairTask() { + private boolean repairTask(List buckets) { LOG.info("Starting quota repair task {}", REPAIR_STATUS); OMMetadataManager activeMetaManager = null; try { @@ -104,7 +110,7 @@ private boolean repairTask() { = OzoneManagerProtocolProtos.QuotaRepairRequest.newBuilder(); // repair active db activeMetaManager = createActiveDBCheckpoint(om.getMetadataManager(), om.getConfiguration()); - repairActiveDb(activeMetaManager, builder); + repairActiveDb(activeMetaManager, builder, buckets); // TODO: repair snapshots for quota @@ -116,12 +122,12 @@ private boolean repairTask() { .setClientId(clientId.toString()) .build(); OzoneManagerProtocolProtos.OMResponse response = submitRequest(omRequest, clientId); - if (response != null && !response.getSuccess()) { + if (response != null && response.getSuccess()) { + REPAIR_STATUS.updateStatus(builder, om.getMetadataManager()); + } else { LOG.error("update quota repair count response failed"); REPAIR_STATUS.updateStatus("Response for update DB is failed"); return false; - } else { - REPAIR_STATUS.updateStatus(builder, om.getMetadataManager()); } } catch (Exception exp) { LOG.error("quota repair count failed", exp); @@ -145,11 +151,15 @@ private boolean repairTask() { private void repairActiveDb( OMMetadataManager metadataManager, - OzoneManagerProtocolProtos.QuotaRepairRequest.Builder builder) throws Exception { + OzoneManagerProtocolProtos.QuotaRepairRequest.Builder builder, + List buckets) throws Exception { Map nameBucketInfoMap = new HashMap<>(); Map idBucketInfoMap = new HashMap<>(); Map oriBucketInfoMap = new HashMap<>(); - prepareAllBucketInfo(nameBucketInfoMap, idBucketInfoMap, oriBucketInfoMap, metadataManager); + prepareAllBucketInfo(nameBucketInfoMap, idBucketInfoMap, oriBucketInfoMap, metadataManager, buckets); + if (nameBucketInfoMap.isEmpty()) { + throw new OMException("no matching buckets", OMException.ResultCodes.BUCKET_NOT_FOUND); + } repairCount(nameBucketInfoMap, idBucketInfoMap, metadataManager); @@ -174,11 +184,15 @@ private void repairActiveDb( } // update volume to support quota - builder.setSupportVolumeOldQuota(true); + if (buckets.isEmpty()) { + builder.setSupportVolumeOldQuota(true); + } else { + builder.setSupportVolumeOldQuota(false); + } } private OzoneManagerProtocolProtos.OMResponse submitRequest( - OzoneManagerProtocolProtos.OMRequest omRequest, ClientId clientId) { + OzoneManagerProtocolProtos.OMRequest omRequest, ClientId clientId) throws Exception { try { if (om.isRatisEnabled()) { OzoneManagerRatisServer server = om.getOmRatisServer(); @@ -186,19 +200,20 @@ private OzoneManagerProtocolProtos.OMResponse submitRequest( .setClientId(clientId) .setServerId(om.getOmRatisServer().getRaftPeerId()) .setGroupId(om.getOmRatisServer().getRaftGroupId()) - .setCallId(runCount.getAndIncrement()) + .setCallId(RUN_CNT.getAndIncrement()) .setMessage(Message.valueOf(OMRatisHelper.convertRequestToByteString(omRequest))) .setType(RaftClientRequest.writeRequestType()) .build(); return server.submitRequest(omRequest, raftClientRequest); } else { + RUN_CNT.getAndIncrement(); return om.getOmServerProtocol().submitRequest( null, omRequest); } } catch (ServiceException e) { LOG.error("repair quota count " + omRequest.getCmdType() + " request failed.", e); + throw e; } - return null; } private OMMetadataManager createActiveDBCheckpoint( @@ -228,24 +243,42 @@ private static String cleanTempCheckPointPath(OMMetadataManager omMetaManager) t private void prepareAllBucketInfo( Map nameBucketInfoMap, Map idBucketInfoMap, - Map oriBucketInfoMap, OMMetadataManager metadataManager) throws IOException { + Map oriBucketInfoMap, OMMetadataManager metadataManager, + List buckets) throws IOException { + if (!buckets.isEmpty()) { + for (String bucketkey : buckets) { + OmBucketInfo bucketInfo = metadataManager.getBucketTable().get(bucketkey); + if (null == bucketInfo) { + continue; + } + populateBucket(nameBucketInfoMap, idBucketInfoMap, oriBucketInfoMap, metadataManager, bucketInfo); + } + return; + } try (TableIterator> iterator = metadataManager.getBucketTable().iterator()) { while (iterator.hasNext()) { Table.KeyValue entry = iterator.next(); OmBucketInfo bucketInfo = entry.getValue(); - String bucketNameKey = buildNamePath(bucketInfo.getVolumeName(), - bucketInfo.getBucketName()); - oriBucketInfoMap.put(bucketNameKey, bucketInfo.copyObject()); - bucketInfo.incrUsedNamespace(-bucketInfo.getUsedNamespace()); - bucketInfo.incrUsedBytes(-bucketInfo.getUsedBytes()); - nameBucketInfoMap.put(bucketNameKey, bucketInfo); - idBucketInfoMap.put(buildIdPath(metadataManager.getVolumeId(bucketInfo.getVolumeName()), - bucketInfo.getObjectID()), bucketInfo); + populateBucket(nameBucketInfoMap, idBucketInfoMap, oriBucketInfoMap, metadataManager, bucketInfo); } } } + private static void populateBucket( + Map nameBucketInfoMap, Map idBucketInfoMap, + Map oriBucketInfoMap, OMMetadataManager metadataManager, + OmBucketInfo bucketInfo) throws IOException { + String bucketNameKey = buildNamePath(bucketInfo.getVolumeName(), + bucketInfo.getBucketName()); + oriBucketInfoMap.put(bucketNameKey, bucketInfo.copyObject()); + bucketInfo.incrUsedNamespace(-bucketInfo.getUsedNamespace()); + bucketInfo.incrUsedBytes(-bucketInfo.getUsedBytes()); + nameBucketInfoMap.put(bucketNameKey, bucketInfo); + idBucketInfoMap.put(buildIdPath(metadataManager.getVolumeId(bucketInfo.getVolumeName()), + bucketInfo.getObjectID()), bucketInfo); + } + private boolean isChange(OmBucketInfo lBucketInfo, OmBucketInfo rBucketInfo) { if (lBucketInfo.getUsedNamespace() != rBucketInfo.getUsedNamespace() || lBucketInfo.getUsedBytes() != rBucketInfo.getUsedBytes()) { @@ -468,8 +501,9 @@ public String toString() { } Map status = new HashMap<>(); status.put("taskId", taskId); - status.put("lastRunStartTime", lastRunStartTime); - status.put("lastRunFinishedTime", lastRunFinishedTime); + status.put("lastRunStartTime", lastRunStartTime > 0 ? new java.util.Date(lastRunStartTime).toString() : ""); + status.put("lastRunFinishedTime", lastRunFinishedTime > 0 ? new java.util.Date(lastRunFinishedTime).toString() + : ""); status.put("errorMsg", errorMsg); status.put("bucketCountDiffMap", bucketCountDiffMap); try { 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 f6bd7cca139b..576fac48c736 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 @@ -382,6 +382,16 @@ public OMResponse handleReadRequest(OMRequest request) { .setServerDefaults(impl.getServerDefaults().getProtobuf()) .build()); break; + case GetQuotaRepairStatus: + OzoneManagerProtocolProtos.GetQuotaRepairStatusResponse quotaRepairStatusRsp = + getQuotaRepairStatus(request.getGetQuotaRepairStatusRequest()); + responseBuilder.setGetQuotaRepairStatusResponse(quotaRepairStatusRsp); + break; + case StartQuotaRepair: + OzoneManagerProtocolProtos.StartQuotaRepairResponse startQuotaRepairRsp = + startQuotaRepair(request.getStartQuotaRepairRequest()); + responseBuilder.setStartQuotaRepairResponse(startQuotaRepairRsp); + break; default: responseBuilder.setSuccess(false); responseBuilder.setMessage("Unrecognized Command Type: " + cmdType); @@ -1521,4 +1531,16 @@ private SafeModeAction toSafeModeAction( safeMode); } } + + private OzoneManagerProtocolProtos.GetQuotaRepairStatusResponse getQuotaRepairStatus( + OzoneManagerProtocolProtos.GetQuotaRepairStatusRequest req) throws IOException { + return OzoneManagerProtocolProtos.GetQuotaRepairStatusResponse.newBuilder() + .setStatus(impl.getQuotaRepairStatus()) + .build(); + } + private OzoneManagerProtocolProtos.StartQuotaRepairResponse startQuotaRepair( + OzoneManagerProtocolProtos.StartQuotaRepairRequest req) throws IOException { + impl.startQuotaRepair(req.getBucketsList()); + return OzoneManagerProtocolProtos.StartQuotaRepairResponse.newBuilder().build(); + } } diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/repair/quota/QuotaRepair.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/repair/quota/QuotaRepair.java new file mode 100644 index 000000000000..5f21b739c818 --- /dev/null +++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/repair/quota/QuotaRepair.java @@ -0,0 +1,121 @@ +/* + * 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.repair.quota; + +import java.io.IOException; +import java.util.Collection; +import java.util.concurrent.Callable; +import org.apache.hadoop.hdds.cli.GenericCli; +import org.apache.hadoop.hdds.cli.SubcommandWithParent; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; +import org.apache.hadoop.ipc.ProtobufRpcEngine; +import org.apache.hadoop.ipc.RPC; +import org.apache.hadoop.ozone.OmUtils; +import org.apache.hadoop.ozone.client.OzoneClientException; +import org.apache.hadoop.ozone.om.protocolPB.Hadoop3OmTransportFactory; +import org.apache.hadoop.ozone.om.protocolPB.OmTransport; +import org.apache.hadoop.ozone.om.protocolPB.OzoneManagerProtocolClientSideTranslatorPB; +import org.apache.hadoop.ozone.om.protocolPB.OzoneManagerProtocolPB; +import org.apache.hadoop.ozone.repair.OzoneRepair; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.ratis.protocol.ClientId; +import org.kohsuke.MetaInfServices; +import picocli.CommandLine; + +import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_ADDRESS_KEY; +import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_SERVICE_IDS_KEY; + +/** + * Ozone Repair CLI for quota. + */ +@CommandLine.Command(name = "quota", + description = "Operational tool to repair quota in OM DB.") +@MetaInfServices(SubcommandWithParent.class) +public class QuotaRepair implements Callable, SubcommandWithParent { + + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; + + @CommandLine.ParentCommand + private OzoneRepair parent; + + @Override + public Void call() { + GenericCli.missingSubcommand(spec); + return null; + } + + public OzoneManagerProtocolClientSideTranslatorPB createOmClient( + String omServiceID, + String omHost, + boolean forceHA + ) throws Exception { + OzoneConfiguration conf = parent.getOzoneConf(); + if (omHost != null && !omHost.isEmpty()) { + omServiceID = null; + conf.set(OZONE_OM_ADDRESS_KEY, omHost); + } else if (omServiceID == null || omServiceID.isEmpty()) { + omServiceID = getTheOnlyConfiguredOmServiceIdOrThrow(); + } + RPC.setProtocolEngine(conf, OzoneManagerProtocolPB.class, + ProtobufRpcEngine.class); + String clientId = ClientId.randomId().toString(); + if (!forceHA || (forceHA && OmUtils.isOmHAServiceId(conf, omServiceID))) { + OmTransport omTransport = new Hadoop3OmTransportFactory() + .createOmTransport(conf, getUser(), omServiceID); + return new OzoneManagerProtocolClientSideTranslatorPB(omTransport, + clientId); + } else { + throw new OzoneClientException("This command works only on OzoneManager" + + " HA cluster. Service ID specified does not match" + + " with " + OZONE_OM_SERVICE_IDS_KEY + " defined in the " + + "configuration. Configured " + OZONE_OM_SERVICE_IDS_KEY + " are " + + conf.getTrimmedStringCollection(OZONE_OM_SERVICE_IDS_KEY) + "\n"); + } + } + + private String getTheOnlyConfiguredOmServiceIdOrThrow() { + if (getConfiguredServiceIds().size() != 1) { + throw new IllegalArgumentException("There is no Ozone Manager service ID " + + "specified, but there are either zero, or more than one service ID" + + "configured."); + } + return getConfiguredServiceIds().iterator().next(); + } + + private Collection getConfiguredServiceIds() { + OzoneConfiguration conf = parent.getOzoneConf(); + Collection omServiceIds = + conf.getTrimmedStringCollection(OZONE_OM_SERVICE_IDS_KEY); + return omServiceIds; + } + + public UserGroupInformation getUser() throws IOException { + return UserGroupInformation.getCurrentUser(); + } + + protected OzoneRepair getParent() { + return parent; + } + + @Override + public Class getParentType() { + return OzoneRepair.class; + } +} diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/repair/quota/QuotaStatus.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/repair/quota/QuotaStatus.java new file mode 100644 index 000000000000..a78d248e055c --- /dev/null +++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/repair/quota/QuotaStatus.java @@ -0,0 +1,80 @@ +/** + * 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.repair.quota; + +import java.util.concurrent.Callable; +import org.apache.hadoop.hdds.cli.HddsVersionProvider; +import org.apache.hadoop.hdds.cli.SubcommandWithParent; +import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol; +import org.kohsuke.MetaInfServices; +import picocli.CommandLine; + + +/** + * Tool to get status of last triggered quota repair. + */ +@CommandLine.Command( + name = "status", + description = "CLI to get the status of last trigger quota repair if available.", + mixinStandardHelpOptions = true, + versionProvider = HddsVersionProvider.class +) +@MetaInfServices(SubcommandWithParent.class) +public class QuotaStatus implements Callable, SubcommandWithParent { + @CommandLine.Spec + private static CommandLine.Model.CommandSpec spec; + + @CommandLine.Option( + names = {"--service-id", "--om-service-id"}, + description = "Ozone Manager Service ID", + required = false + ) + private String omServiceId; + + @CommandLine.Option( + names = {"--service-host"}, + description = "Ozone Manager Host. If OM HA is enabled, use --service-id instead. " + + "If you must use --service-host with OM HA, this must point directly to the leader OM. " + + "This option is required when --service-id is not provided or when HA is not enabled." + ) + private String omHost; + + @CommandLine.ParentCommand + private QuotaRepair parent; + + @Override + public Void call() throws Exception { + OzoneManagerProtocol ozoneManagerClient = + parent.createOmClient(omServiceId, omHost, false); + System.out.println(ozoneManagerClient.getQuotaRepairStatus()); + return null; + } + + protected QuotaRepair getParent() { + return parent; + } + + @Override + public Class getParentType() { + return QuotaRepair.class; + } +} diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/repair/quota/QuotaTrigger.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/repair/quota/QuotaTrigger.java new file mode 100644 index 000000000000..19ad92340c06 --- /dev/null +++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/repair/quota/QuotaTrigger.java @@ -0,0 +1,100 @@ +/* + * 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.repair.quota; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; +import org.apache.commons.lang3.StringUtils; +import org.apache.hadoop.hdds.cli.HddsVersionProvider; +import org.apache.hadoop.hdds.cli.SubcommandWithParent; +import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol; +import org.kohsuke.MetaInfServices; +import picocli.CommandLine; + +/** + * Tool to trigger quota repair. + */ +@CommandLine.Command( + name = "start", + description = "CLI to trigger quota repair.", + mixinStandardHelpOptions = true, + versionProvider = HddsVersionProvider.class +) +@MetaInfServices(SubcommandWithParent.class) +public class QuotaTrigger implements Callable, SubcommandWithParent { + @CommandLine.Spec + private static CommandLine.Model.CommandSpec spec; + + @CommandLine.ParentCommand + private QuotaRepair parent; + + @CommandLine.Option( + names = {"--service-id", "--om-service-id"}, + description = "Ozone Manager Service ID", + required = false + ) + private String omServiceId; + + @CommandLine.Option( + names = {"--service-host"}, + description = "Ozone Manager Host. If OM HA is enabled, use --service-id instead. " + + "If you must use --service-host with OM HA, this must point directly to the leader OM. " + + "This option is required when --service-id is not provided or when HA is not enabled." + ) + private String omHost; + + @CommandLine.Option(names = {"--buckets"}, + required = false, + description = "start quota repair for specific buckets. Input will be list of uri separated by comma as" + + " //[,...]") + private String buckets; + + @Override + public Void call() throws Exception { + List bucketList = Collections.emptyList(); + if (StringUtils.isNotEmpty(buckets)) { + bucketList = Arrays.asList(buckets.split(",")); + } + + OzoneManagerProtocol ozoneManagerClient = + parent.createOmClient(omServiceId, omHost, false); + try { + ozoneManagerClient.startQuotaRepair(bucketList); + System.out.println(ozoneManagerClient.getQuotaRepairStatus()); + } catch (Exception ex) { + System.out.println(ex.getMessage()); + } + return null; + } + + protected QuotaRepair getParent() { + return parent; + } + + @Override + public Class getParentType() { + return QuotaRepair.class; + } + +} diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/repair/quota/package-info.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/repair/quota/package-info.java new file mode 100644 index 000000000000..9a433b24397a --- /dev/null +++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/repair/quota/package-info.java @@ -0,0 +1,22 @@ +/* + * 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. + */ + +/** + * Ozone Repair tools. + */ +package org.apache.hadoop.ozone.repair.quota;