This is an automated email from the ASF dual-hosted git repository.
chungen0126 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/master by this push:
new 24e06a40c16 HDDS-15835. Count incomplete MPU parts in quota repair
(#10795)
24e06a40c16 is described below
commit 24e06a40c16df03757031acfdc94ddf01642890d
Author: Chi-Hsuan Huang <[email protected]>
AuthorDate: Wed Sep 30 16:20:24 2026 +0800
HDDS-15835. Count incomplete MPU parts in quota repair (#10795)
---
.../hadoop/ozone/om/service/QuotaRepairTask.java | 64 ++++-
.../ozone/om/service/TestQuotaRepairTask.java | 306 +++++++++++++++++++++
2 files changed, 367 insertions(+), 3 deletions(-)
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 45e41e4dc34..bc800b5a5b2 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
@@ -38,6 +38,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.SortedMap;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
@@ -68,9 +69,13 @@
import org.apache.hadoop.ozone.om.helpers.BucketLayout;
import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.OmMultipartKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.OmMultipartPartInfo;
+import org.apache.hadoop.ozone.om.helpers.QuotaUtil;
import org.apache.hadoop.ozone.om.helpers.RepeatedOmKeyInfo;
import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
import org.apache.hadoop.ozone.om.ratis.utils.OzoneManagerRatisUtils;
+import org.apache.hadoop.ozone.om.request.util.OMMultipartUploadUtils;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
import org.apache.hadoop.util.Time;
import org.apache.ratis.protocol.ClientId;
@@ -89,9 +94,9 @@ public class QuotaRepairTask {
private static final int TASK_THREAD_CNT = 3;
/**
* Parallel full-table scans: OBS keys, FSO files, dirs, active deleted
keys/dirs,
- * snapshot DB deleted keys/dirs.
+ * snapshot DB deleted keys/dirs, multipart upload parts.
*/
- private static final int QUOTA_REPAIR_SCAN_TASKS = 6;
+ private static final int QUOTA_REPAIR_SCAN_TASKS = 7;
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);
@@ -332,6 +337,7 @@ private void repairCount(
Map<String, CountPair> directoryCountMap = new ConcurrentHashMap<>();
Map<String, CountPair> snapshotDeletedKeyMap = new ConcurrentHashMap<>();
Map<String, CountPair> snapshotDeletedDirMap = new ConcurrentHashMap<>();
+ Map<String, CountPair> mpuCountMap = new ConcurrentHashMap<>();
try {
nameBucketInfoMap.keySet().stream().forEach(e -> keyCountMap.put(e,
new CountPair()));
@@ -341,6 +347,7 @@ private void repairCount(
new CountPair()));
nameBucketInfoMap.keySet().forEach(k -> snapshotDeletedKeyMap.put(k, new
CountPair()));
idBucketInfoMap.keySet().forEach(k -> snapshotDeletedDirMap.put(k, new
CountPair()));
+ nameBucketInfoMap.keySet().forEach(k -> mpuCountMap.put(k, new
CountPair()));
List<Future<?>> tasks = new ArrayList<>();
tasks.add(executor.submit(() -> recalculateUsages(
@@ -369,6 +376,7 @@ private void repairCount(
throw new UncheckedIOException(ex);
}
}));
+ tasks.add(executor.submit(() ->
recalculateMultipartUsages(metadataManager, mpuCountMap)));
// await every scan before propagating a failure, so no scan outlives
the checkpoint
Exception scanFailure = awaitAll(tasks, null);
@@ -387,6 +395,7 @@ private void repairCount(
updateCountToBucketInfo(nameBucketInfoMap, keyCountMap);
updateCountToBucketInfo(idBucketInfoMap, fileCountMap);
updateCountToBucketInfo(idBucketInfoMap, directoryCountMap);
+ updateCountToBucketInfo(nameBucketInfoMap, mpuCountMap);
mergeSnapshotDeletedTableCounts(nameBucketInfoMap, snapshotDeletedKeyMap);
mergeDeletedDirSnapshotNamespace(idBucketInfoMap, snapshotDeletedDirMap);
LOG.info("Completed quota repair counting for all keys, files and
directories");
@@ -558,6 +567,54 @@ private void recalculateDeletedDirNamespace(
}
}
+ /**
+ * Counts committed parts of incomplete multipart uploads from the active DB
checkpoint.
+ * Parts of a snapshot's incomplete uploads stay charged to that snapshot's
own bucket,
+ * so only the active multipartInfoTable is scanned here. The scan reads
keys only and
+ * each counting task reads the row it needs, so a row is never decoded for
a bucket
+ * outside the repair and the batches hold keys instead of parts.
+ */
+ private void recalculateMultipartUsages(
+ OMMetadataManager metadataManager, Map<String, CountPair> mpuCountMap)
+ throws UncheckedIOException, UncheckedExecutionException {
+ try (Table.KeyValueIterator<String, OmMultipartKeyInfo> keyIter
+ = metadataManager.getMultipartInfoTable().iterator(null, KEY_ONLY)) {
+ scanTableInBatches(executor, keyIter, "Multipart upload usages",
+ kv -> extractMultipartCount(kv.getKey(), mpuCountMap,
metadataManager));
+ } catch (IOException ex) {
+ throw new UncheckedIOException(ex);
+ }
+ }
+
+ @VisibleForTesting
+ static void extractMultipartCount(
+ String key, Map<String, CountPair> mpuCountMap,
+ OMMetadataManager metadataManager) throws UncheckedIOException {
+ try {
+ CountPair usage = mpuCountMap.get(getVolumeBucketPrefix(key));
+ if (usage == null) {
+ return;
+ }
+ OmMultipartKeyInfo multipartKeyInfo =
+ metadataManager.getMultipartInfoTable().getSkipCache(key);
+ long replicatedSize = 0;
+ if (multipartKeyInfo.getSchemaVersion() ==
OmMultipartKeyInfo.LEGACY_SCHEMA_VERSION) {
+ for (OzoneManagerProtocolProtos.PartKeyInfo partKeyInfo :
multipartKeyInfo.getPartKeyInfoMap()) {
+ replicatedSize += QuotaUtil.getReplicatedSize(
+ partKeyInfo.getPartKeyInfo().getDataSize(),
multipartKeyInfo.getReplicationConfig());
+ }
+ } else {
+ SortedMap<Integer, OmMultipartPartInfo> parts =
+ OMMultipartUploadUtils.scanParts(metadataManager,
multipartKeyInfo.getUploadID());
+ replicatedSize = OMMultipartUploadUtils.getReplicatedSize(
+ parts, multipartKeyInfo.getReplicationConfig());
+ }
+ usage.incrSpace(replicatedSize);
+ } catch (IOException ex) {
+ throw new UncheckedIOException(ex);
+ }
+ }
+
private static synchronized void mergeSnapshotDeletedTableCounts(
Map<String, OmBucketInfo> nameBucketInfoMap,
Map<String, CountPair> counts) {
@@ -763,7 +820,8 @@ private static String getVolumeBucketPrefix(String key) {
return prefix;
}
- private static class CountPair {
+ @VisibleForTesting
+ static class CountPair {
private AtomicLong space = new AtomicLong();
private AtomicLong namespace = new AtomicLong();
diff --git
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestQuotaRepairTask.java
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestQuotaRepairTask.java
index 00b01b18c44..3565484b4c5 100644
---
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestQuotaRepairTask.java
+++
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestQuotaRepairTask.java
@@ -29,11 +29,17 @@
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import com.google.common.util.concurrent.UncheckedExecutionException;
import java.io.IOException;
import java.io.UncheckedIOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
@@ -41,23 +47,36 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
+import org.apache.hadoop.hdds.client.ECReplicationConfig;
import org.apache.hadoop.hdds.client.RatisReplicationConfig;
+import org.apache.hadoop.hdds.client.ReplicationConfig;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
import org.apache.hadoop.hdds.utils.db.BatchOperation;
import org.apache.hadoop.hdds.utils.db.Table;
import org.apache.hadoop.hdds.utils.db.cache.CacheKey;
import org.apache.hadoop.hdds.utils.db.cache.CacheValue;
+import org.apache.hadoop.ozone.OzoneConsts;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
import org.apache.hadoop.ozone.om.helpers.BucketLayout;
import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.OmMultipartKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.OmMultipartPartInfo;
+import org.apache.hadoop.ozone.om.helpers.OmMultipartPartKey;
+import org.apache.hadoop.ozone.om.helpers.OmMultipartUpload;
import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs;
import org.apache.hadoop.ozone.om.helpers.RepeatedOmKeyInfo;
import org.apache.hadoop.ozone.om.ratis.OzoneManagerRatisServer;
import org.apache.hadoop.ozone.om.request.OMRequestTestUtils;
import org.apache.hadoop.ozone.om.request.key.OMKeyRequestTests;
+import
org.apache.hadoop.ozone.om.request.s3.multipart.S3MultipartUploadAbortRequest;
+import
org.apache.hadoop.ozone.om.request.s3.multipart.S3MultipartUploadAbortRequestWithFSO;
import org.apache.hadoop.ozone.om.request.volume.OMQuotaRepairRequest;
import org.apache.hadoop.ozone.om.response.OMClientResponse;
import org.apache.hadoop.ozone.om.response.volume.OMQuotaRepairResponse;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.KeyInfo;
+import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.PartKeyInfo;
import org.apache.hadoop.util.Time;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
@@ -70,6 +89,7 @@ public class TestQuotaRepairTask extends OMKeyRequestTests {
/** Seconds; must match {@link Timeout} on this class. */
private static final int REPAIR_TEST_TIMEOUT_SECONDS = 120;
+ private static final ReplicationConfig RATIS_ONE =
RatisReplicationConfig.getInstance(ONE);
private static Boolean awaitRepair(CompletableFuture<Boolean> repair) throws
Exception {
return repair.get(REPAIR_TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
@@ -449,4 +469,290 @@ public void testInterruptedScanStillAwaitsWorkers()
throws Exception {
executor.shutdownNow();
}
}
+
+ @Test
+ public void testQuotaRepairCountsMPUParts() throws Exception {
+ AtomicReference<OzoneManagerProtocolProtos.OMRequest> request =
mockQuotaRepairRequest();
+ OMRequestTestUtils.addVolumeAndBucketToDB(volumeName, bucketName,
+ omMetadataManager, BucketLayout.OBJECT_STORE);
+ String otherBucketName = "other" + bucketName;
+ OMRequestTestUtils.addBucketToDB(volumeName, otherBucketName,
+ omMetadataManager, BucketLayout.OBJECT_STORE);
+
+ // committed keys stay charged after the MPU parts are released
+ int keyCount = 3;
+ for (int i = 0; i < keyCount; i++) {
+ OMRequestTestUtils.addKeyToTableAndCache(volumeName, bucketName, "key" +
i, -1,
+ RatisReplicationConfig.getInstance(ONE), 10L + i, omMetadataManager);
+ }
+ long keyBytes = keyCount * 1000L;
+
+ // legacy schema: parts are inlined in the upload record, keyed by part
number,
+ // so committing part 1 twice (100 then 300) leaves only the current 300
+ String legacyKey = "legacyMpuKey";
+ String legacyUploadId = UUID.randomUUID().toString();
+ OmMultipartKeyInfo legacyInfo = newMultipartInfo(legacyUploadId, 1001L,
RATIS_ONE, false);
+ legacyInfo.addPartKeyInfo(createPart(bucketName, legacyKey,
legacyUploadId, 1, 100L));
+ legacyInfo.addPartKeyInfo(createPart(bucketName, legacyKey,
legacyUploadId, 1, 300L));
+ legacyInfo.addPartKeyInfo(createPart(bucketName, legacyKey,
legacyUploadId, 2, 200L));
+ addMultipartInfo(bucketName, legacyKey, legacyInfo, 1L);
+ long legacyBytes = 300L + 200L;
+
+ // split-parts schema: parts live in their own table
+ String splitKey = "splitMpuKey";
+ String splitUploadId = UUID.randomUUID().toString();
+ addMultipartInfo(bucketName, splitKey, newMultipartInfo(splitUploadId,
1002L, RATIS_ONE, true), 2L);
+ putSplitPart(bucketName, splitKey, splitUploadId, 1, 400L);
+ putSplitPart(bucketName, splitKey, splitUploadId, 2, 500L);
+ long splitBytes = 400L + 500L;
+
+ // uploads initiated but with no part committed yet must contribute nothing
+ String emptyLegacyUploadId = UUID.randomUUID().toString();
+ addMultipartInfo(bucketName, "emptyLegacyMpuKey",
+ newMultipartInfo(emptyLegacyUploadId, 1003L, RATIS_ONE, false), 3L);
+ String emptySplitUploadId = UUID.randomUUID().toString();
+ addMultipartInfo(bucketName, "emptySplitMpuKey",
+ newMultipartInfo(emptySplitUploadId, 1004L, RATIS_ONE, true), 4L);
+
+ // a second bucket proves parts are charged to the bucket that owns them.
+ // Its upload replicates three ways, so the part size is charged three
times over.
+ String otherKey = "otherMpuKey";
+ String otherUploadId = UUID.randomUUID().toString();
+ OmMultipartKeyInfo otherInfo = newMultipartInfo(otherUploadId, 2001L,
+ RatisReplicationConfig.getInstance(THREE), false);
+ otherInfo.addPartKeyInfo(createPart(otherBucketName, otherKey,
otherUploadId, 1, 700L));
+ addMultipartInfo(otherBucketName, otherKey, otherInfo, 5L);
+ long otherBytes = 700L * 3;
+
+ String bucketKey = corruptBucketUsage(bucketName, 12345L, 99L, 6L);
+ String otherBucketKey = corruptBucketUsage(otherBucketName, 54321L, 77L,
7L);
+ applyQuotaRepair(request, 8L);
+
+ OmBucketInfo repaired = omMetadataManager.getBucketTable().get(bucketKey);
+ assertEquals(keyBytes + legacyBytes + splitBytes, repaired.getUsedBytes());
+ // MPU parts consume no namespace, only the committed keys do
+ assertEquals(keyCount, repaired.getUsedNamespace());
+ OmBucketInfo otherRepaired =
omMetadataManager.getBucketTable().get(otherBucketKey);
+ assertEquals(otherBytes, otherRepaired.getUsedBytes());
+ assertEquals(0L, otherRepaired.getUsedNamespace());
+
+ // aborting through the real request path must release exactly what repair
counted.
+ // No open key is staged, so abort takes its orphan-parts path.
+ abortMpu(bucketName, legacyKey, legacyUploadId, BucketLayout.OBJECT_STORE,
5001L);
+ abortMpu(bucketName, splitKey, splitUploadId, BucketLayout.OBJECT_STORE,
5002L);
+ assertEquals(keyBytes,
omMetadataManager.getBucketTable().get(bucketKey).getUsedBytes());
+ abortMpu(otherBucketName, otherKey, otherUploadId,
BucketLayout.OBJECT_STORE, 5003L);
+ assertEquals(0L,
omMetadataManager.getBucketTable().get(otherBucketKey).getUsedBytes());
+ }
+
+ @Test
+ public void testQuotaRepairCountsMPUPartsWithFSO() throws Exception {
+ AtomicReference<OzoneManagerProtocolProtos.OMRequest> request =
mockQuotaRepairRequest();
+ String fsoBucketName = "fsomup" + bucketName;
+ OMRequestTestUtils.addVolumeAndBucketToDB(volumeName, fsoBucketName,
+ omMetadataManager, BucketLayout.FILE_SYSTEM_OPTIMIZED);
+
+ String keyName = "fsoMpuFile";
+ String uploadId = UUID.randomUUID().toString();
+ OmMultipartKeyInfo mpuInfo = newMultipartInfo(uploadId, 3001L,
+ RatisReplicationConfig.getInstance(THREE), true);
+ addMultipartInfo(fsoBucketName, keyName, mpuInfo, 1L);
+ putSplitPart(fsoBucketName, keyName, uploadId, 1, 100L);
+ long ratisBytes = 100L * 3;
+
+ // EC parity overhead is charged too: one full rs-3-2-1024k stripe of data
+ // costs the 3 MiB of data plus 2 MiB of parity
+ String ecKeyName = "ecMpuFile";
+ String ecUploadId = UUID.randomUUID().toString();
+ OmMultipartKeyInfo ecInfo = newMultipartInfo(ecUploadId, 3002L,
+ new ECReplicationConfig("rs-3-2-1024k"), true);
+ addMultipartInfo(fsoBucketName, ecKeyName, ecInfo, 2L);
+ putSplitPart(fsoBucketName, ecKeyName, ecUploadId, 1, 3 * 1024 * 1024L);
+ long ecBytes = 5 * 1024 * 1024L;
+
+ String bucketKey = corruptBucketUsage(fsoBucketName, 777L, 88L, 3L);
+ applyQuotaRepair(request, 4L);
+
+ OmBucketInfo repaired = omMetadataManager.getBucketTable().get(bucketKey);
+ assertEquals(ratisBytes + ecBytes, repaired.getUsedBytes());
+ assertEquals(0L, repaired.getUsedNamespace());
+
+ abortMpu(fsoBucketName, keyName, uploadId,
BucketLayout.FILE_SYSTEM_OPTIMIZED, 5001L);
+ abortMpu(fsoBucketName, ecKeyName, ecUploadId,
BucketLayout.FILE_SYSTEM_OPTIMIZED, 5002L);
+ assertEquals(0L,
omMetadataManager.getBucketTable().get(bucketKey).getUsedBytes());
+ }
+
+ @Test
+ public void testQuotaRepairSkipsMPUOutsideRequestedBuckets() throws
Exception {
+ AtomicReference<OzoneManagerProtocolProtos.OMRequest> request =
mockQuotaRepairRequest();
+ OMRequestTestUtils.addVolumeAndBucketToDB(volumeName, bucketName,
+ omMetadataManager, BucketLayout.OBJECT_STORE);
+ String skippedBucketName = "skipped" + bucketName;
+ OMRequestTestUtils.addBucketToDB(volumeName, skippedBucketName,
+ omMetadataManager, BucketLayout.OBJECT_STORE);
+
+ String repairedKey = "repairedMpuKey";
+ String repairedUploadId = UUID.randomUUID().toString();
+ OmMultipartKeyInfo repairedInfo = newMultipartInfo(repairedUploadId,
4001L, RATIS_ONE, false);
+ repairedInfo.addPartKeyInfo(createPart(bucketName, repairedKey,
repairedUploadId, 1, 500L));
+ addMultipartInfo(bucketName, repairedKey, repairedInfo, 1L);
+
+ String skippedKey = "skippedMpuKey";
+ String skippedUploadId = UUID.randomUUID().toString();
+ OmMultipartKeyInfo skippedInfo = newMultipartInfo(skippedUploadId, 4002L,
RATIS_ONE, false);
+ skippedInfo.addPartKeyInfo(createPart(skippedBucketName, skippedKey,
skippedUploadId, 1, 900L));
+ addMultipartInfo(skippedBucketName, skippedKey, skippedInfo, 2L);
+
+ String bucketKey = corruptBucketUsage(bucketName, 12345L, 99L, 3L);
+ String skippedBucketKey = corruptBucketUsage(skippedBucketName, 54321L,
77L, 4L);
+ applyQuotaRepair(request, 5L, bucketKey);
+
+ assertEquals(500L,
omMetadataManager.getBucketTable().get(bucketKey).getUsedBytes());
+ // the unrequested bucket keeps its stored usage, its parts are not folded
in anywhere
+ OmBucketInfo skipped =
omMetadataManager.getBucketTable().get(skippedBucketKey);
+ assertEquals(54321L, skipped.getUsedBytes());
+ assertEquals(77L, skipped.getUsedNamespace());
+ }
+
+ @Test
+ public void testMultipartCountLeavesUnrequestedBucketRowUnread() {
+ Map<String, QuotaRepairTask.CountPair> mpuCountMap = new HashMap<>();
+ mpuCountMap.put(OzoneConsts.OM_KEY_PREFIX + volumeName +
OzoneConsts.OM_KEY_PREFIX + bucketName
+ + OzoneConsts.OM_KEY_PREFIX, new QuotaRepairTask.CountPair());
+ OMMetadataManager metadataManager = mock(OMMetadataManager.class);
+
+ QuotaRepairTask.extractMultipartCount(
+ OmMultipartUpload.getDbKey(volumeName, "other" + bucketName, "mpuKey",
UUID.randomUUID().toString()),
+ mpuCountMap, metadataManager);
+
+ // the bucket is filtered on the key, so the row is never fetched or
decoded
+ verifyNoInteractions(metadataManager);
+ }
+
+ @Test
+ public void testMultipartCountReadsRequestedBucketRow() throws Exception {
+ QuotaRepairTask.CountPair usage = new QuotaRepairTask.CountPair();
+ Map<String, QuotaRepairTask.CountPair> mpuCountMap = new HashMap<>();
+ mpuCountMap.put(OzoneConsts.OM_KEY_PREFIX + volumeName +
OzoneConsts.OM_KEY_PREFIX + bucketName
+ + OzoneConsts.OM_KEY_PREFIX, usage);
+
+ String keyName = "mpuKey";
+ String uploadId = UUID.randomUUID().toString();
+ OmMultipartKeyInfo multipartInfo = newMultipartInfo(uploadId, 4003L,
+ RatisReplicationConfig.getInstance(THREE), false);
+ multipartInfo.addPartKeyInfo(createPart(bucketName, keyName, uploadId, 1,
100L));
+ multipartInfo.addPartKeyInfo(createPart(bucketName, keyName, uploadId, 2,
200L));
+ String dbKey = OmMultipartUpload.getDbKey(volumeName, bucketName, keyName,
uploadId);
+
+ Table<String, OmMultipartKeyInfo> multipartInfoTable = mock(Table.class);
+ when(multipartInfoTable.getSkipCache(dbKey)).thenReturn(multipartInfo);
+ OMMetadataManager metadataManager = mock(OMMetadataManager.class);
+
when(metadataManager.getMultipartInfoTable()).thenReturn(multipartInfoTable);
+
+ QuotaRepairTask.extractMultipartCount(dbKey, mpuCountMap, metadataManager);
+
+ assertEquals((100L + 200L) * 3, usage.getSpace());
+ verify(multipartInfoTable).getSkipCache(dbKey);
+ }
+
+ private AtomicReference<OzoneManagerProtocolProtos.OMRequest>
mockQuotaRepairRequest() throws Exception {
+ OzoneManagerProtocolProtos.OMResponse response =
mock(OzoneManagerProtocolProtos.OMResponse.class);
+ when(response.getSuccess()).thenReturn(true);
+ OzoneManagerRatisServer ratisServer = mock(OzoneManagerRatisServer.class);
+ AtomicReference<OzoneManagerProtocolProtos.OMRequest> request = new
AtomicReference<>();
+ doAnswer(invocation -> {
+ request.set(invocation.getArgument(0,
OzoneManagerProtocolProtos.OMRequest.class));
+ return response;
+ }).when(ratisServer).submitRequest(any(), any(), anyLong());
+ when(ozoneManager.getOmRatisServer()).thenReturn(ratisServer);
+ return request;
+ }
+
+ private OmMultipartKeyInfo newMultipartInfo(String uploadId, long objectId,
+ ReplicationConfig replicationConfig, boolean splitParts) {
+ OmMultipartKeyInfo.Builder builder =
OMRequestTestUtils.createOmMultipartKeyInfo(
+ uploadId, Time.now(), HddsProtos.ReplicationType.RATIS,
+ HddsProtos.ReplicationFactor.ONE, objectId).toBuilder()
+ .setReplicationConfig(replicationConfig);
+ if (splitParts) {
+
builder.setSchemaVersion(OmMultipartKeyInfo.SPLIT_PARTS_TABLE_SCHEMA_VERSION);
+ }
+ return builder.build();
+ }
+
+ private void addMultipartInfo(String bucket, String keyName,
OmMultipartKeyInfo multipartInfo,
+ long transactionIndex) throws IOException {
+ OmKeyInfo omKeyInfo = OMRequestTestUtils.createOmKeyInfo(volumeName,
bucket,
+ keyName, multipartInfo.getReplicationConfig()).build();
+ OMRequestTestUtils.addMultipartInfoToTable(false, omKeyInfo,
multipartInfo, transactionIndex,
+ omMetadataManager);
+ }
+
+ // repair scans a RocksDB checkpoint, so parts must be written to the table,
not the cache
+ private void putSplitPart(String bucket, String keyName, String uploadId,
int partNumber,
+ long dataSize) throws IOException {
+
omMetadataManager.getMultipartPartsTable().put(OmMultipartPartKey.of(uploadId,
partNumber),
+ createSplitPart(bucket, keyName, uploadId, partNumber, dataSize));
+ }
+
+ private String corruptBucketUsage(String bucket, long usedBytes, long
usedNamespace, long transactionIndex)
+ throws IOException {
+ String bucketKey = omMetadataManager.getBucketKey(volumeName, bucket);
+ OmBucketInfo corrupted =
omMetadataManager.getBucketTable().get(bucketKey).toBuilder()
+ .setUsedBytes(usedBytes).setUsedNamespace(usedNamespace).build();
+ omMetadataManager.getBucketTable().put(bucketKey, corrupted);
+ omMetadataManager.getBucketTable().addCacheEntry(
+ new CacheKey<>(bucketKey), CacheValue.get(transactionIndex,
corrupted));
+ return bucketKey;
+ }
+
+ private void
applyQuotaRepair(AtomicReference<OzoneManagerProtocolProtos.OMRequest> request,
+ long transactionIndex, String... buckets) throws Exception {
+ QuotaRepairTask quotaRepairTask = new QuotaRepairTask(ozoneManager);
+ assertTrue(awaitRepair(quotaRepairTask.repair(Arrays.asList(buckets))));
+ OMClientResponse response = new OMQuotaRepairRequest(request.get())
+ .validateAndUpdateCache(ozoneManager, transactionIndex);
+ BatchOperation batchOperation =
omMetadataManager.getStore().initBatchOperation();
+ ((OMQuotaRepairResponse) response).addToDBBatch(omMetadataManager,
batchOperation);
+ omMetadataManager.getStore().commitBatchOperation(batchOperation);
+ }
+
+ private void abortMpu(String bucket, String keyName, String uploadId,
BucketLayout layout,
+ long transactionIndex) throws Exception {
+ OzoneManagerProtocolProtos.OMRequest abortRequest =
OMRequestTestUtils.createAbortMPURequest(
+ volumeName, bucket, keyName, uploadId);
+ OzoneManagerProtocolProtos.OMRequest preExecuted;
+ OMClientResponse response;
+ if (layout == BucketLayout.FILE_SYSTEM_OPTIMIZED) {
+ preExecuted = new S3MultipartUploadAbortRequestWithFSO(abortRequest,
layout).preExecute(ozoneManager);
+ response = new S3MultipartUploadAbortRequestWithFSO(preExecuted, layout)
+ .validateAndUpdateCache(ozoneManager, transactionIndex);
+ } else {
+ preExecuted = new S3MultipartUploadAbortRequest(abortRequest,
layout).preExecute(ozoneManager);
+ response = new S3MultipartUploadAbortRequest(preExecuted, layout)
+ .validateAndUpdateCache(ozoneManager, transactionIndex);
+ }
+ assertTrue(response.getOMResponse().getSuccess());
+ }
+
+ private PartKeyInfo createPart(String bucket, String keyName, String
uploadId, int partNumber,
+ long dataSize) {
+ return PartKeyInfo.newBuilder().setPartNumber(partNumber)
+ .setPartName(OmMultipartUpload.getDbKey(volumeName, bucket, keyName,
uploadId) + "/" + partNumber)
+
.setPartKeyInfo(KeyInfo.newBuilder().setVolumeName(volumeName).setBucketName(bucket)
+
.setKeyName(keyName).setDataSize(dataSize).setCreationTime(Time.now()).setModificationTime(Time.now())
+ .setType(HddsProtos.ReplicationType.RATIS).setFactor(ONE).build())
+ .build();
+ }
+
+ private OmMultipartPartInfo createSplitPart(String bucket, String keyName,
String uploadId,
+ int partNumber, long dataSize) {
+ OmKeyInfo partKeyInfo = OMRequestTestUtils.createOmKeyInfo(volumeName,
bucket, keyName,
+ RatisReplicationConfig.getInstance(ONE)).setDataSize(dataSize)
+ .setObjectID(2000L + partNumber).setUpdateID(2000L + partNumber)
+ .addMetadata(OzoneConsts.ETAG, "etag-" + partNumber).build();
+ return OmMultipartPartInfo.from(
+ OmMultipartUpload.getDbKey(volumeName, bucket, keyName, uploadId) +
"/" + partNumber,
+ partNumber, partKeyInfo);
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]