Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@
import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
import org.apache.hadoop.ozone.om.helpers.OmDirectoryInfo;
import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfoGroup;
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.OmVolumeArgs;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.PartKeyInfo;
import picocli.CommandLine;
Expand Down Expand Up @@ -100,6 +103,7 @@ public class ContainerToKeyMapping extends AbstractSubcommand implements Callabl
private Table<String, OmKeyInfo> openFileTable;
private Table<String, OmKeyInfo> openKeyTable;
private Table<String, OmMultipartKeyInfo> multipartInfoTable;
private Table<OmMultipartPartKey, OmMultipartPartInfo> multipartPartsTable;
private DBStore dirTreeDbStore;
private Table<Long, String> dirTreeTable;
// Cache volume IDs to avoid repeated lookups
Expand Down Expand Up @@ -138,6 +142,7 @@ public Void call() throws Exception {
openFileTable = OMDBDefinition.OPEN_FILE_TABLE_DEF.getTable(omDbStore, CacheType.NO_CACHE);
openKeyTable = OMDBDefinition.OPEN_KEY_TABLE_DEF.getTable(omDbStore, CacheType.NO_CACHE);
multipartInfoTable = OMDBDefinition.MULTIPART_INFO_TABLE_DEF.getTable(omDbStore, CacheType.NO_CACHE);
multipartPartsTable = OMDBDefinition.MULTIPART_PARTS_TABLE_DEF.getTable(omDbStore, CacheType.NO_CACHE);

retrieve(dbPath, writer, containerIDs);
} catch (Exception e) {
Expand Down Expand Up @@ -313,11 +318,18 @@ private void processMultipartUpload(Set<Long> containerIds, Map<Long, List<Strin
String dbKey = entry.getKey();
OmMultipartKeyInfo mpuInfo = entry.getValue();

// Collect all target containers that have parts of this MPU
// Collect all target containers that have parts of this MPU. Legacy
// (schemaVersion 0) MPUs embed their parts in the multipartInfoTable
// value, whereas split (schemaVersion 1) MPUs store each part as a
// separate row in the multipartPartsTable.
Set<Long> matchedContainers = new HashSet<>();
for (PartKeyInfo partKeyInfo : mpuInfo.getPartKeyInfoMap()) {
OmKeyInfo partKey = OmKeyInfo.getFromProtobuf(partKeyInfo.getPartKeyInfo());
matchedContainers.addAll(getKeyContainers(partKey, containerIds));
if (mpuInfo.getSchemaVersion() == OmMultipartKeyInfo.SPLIT_PARTS_TABLE_SCHEMA_VERSION) {
matchedContainers.addAll(getSplitPartContainers(dbKey, containerIds));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add test for the new code path. Thanks!

TestOmMetadataManager
TestContainerToKeyMapping
TestKeyLifecycleService

} else {
for (PartKeyInfo partKeyInfo : mpuInfo.getPartKeyInfoMap()) {
OmKeyInfo partKey = OmKeyInfo.getFromProtobuf(partKeyInfo.getPartKeyInfo());
matchedContainers.addAll(getKeyContainers(partKey, containerIds));
}
}

if (!matchedContainers.isEmpty()) {
Expand All @@ -332,8 +344,43 @@ private void processMultipartUpload(Set<Long> containerIds, Map<Long, List<Strin
}

private Set<Long> getKeyContainers(OmKeyInfo keyInfo, Set<Long> targetContainerIds) {
return getContainers(keyInfo.getKeyLocationVersions(), targetContainerIds);
}

/**
* Scans the split multipartPartsTable for all parts belonging to the given
* multipart upload (its uploadId is the last path component of the
* multipartInfoTable db key) and returns the target containers referenced by
* those parts' block locations.
*/
private Set<Long> getSplitPartContainers(String multipartInfoDbKey, Set<Long> targetContainerIds) {
Set<Long> matchedContainers = new HashSet<>();
String uploadId = multipartInfoDbKey.substring(
multipartInfoDbKey.lastIndexOf(OM_KEY_PREFIX) + OM_KEY_PREFIX.length());
Comment on lines +358 to +359

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the below approach instead of manual substring parsing -

Suggested change
String uploadId = multipartInfoDbKey.substring(
multipartInfoDbKey.lastIndexOf(OM_KEY_PREFIX) + OM_KEY_PREFIX.length());
String uploadId;
try {
uploadId = OmMultipartUpload.from(multipartInfoDbKey).getUploadId();
} catch (IllegalArgumentException e) {
err().println("Invalid multipartInfoTable key " + multipartInfoDbKey + ", " + e);
return matchedContainers;
}

OmMultipartPartKey prefix = OmMultipartPartKey.prefix(uploadId);
try (TableIterator<OmMultipartPartKey, Table.KeyValue<OmMultipartPartKey, OmMultipartPartInfo>>
partIterator = multipartPartsTable.iterator(prefix)) {
while (partIterator.hasNext()) {
Table.KeyValue<OmMultipartPartKey, OmMultipartPartInfo> partEntry = partIterator.next();
OmMultipartPartKey partKey = partEntry.getKey();
// Prefix iteration can overshoot into the next upload's rows; stop then.
if (!uploadId.equals(partKey.getUploadId())) {
break;
}
if (partKey.hasPartNumber()) {
matchedContainers.addAll(
getContainers(partEntry.getValue().getKeyLocationInfos(), targetContainerIds));
}
}
} catch (Exception e) {
err().println("Exception occurred reading multipartPartsTable for upload " + uploadId + ", " + e);
}
return matchedContainers;
}

private Set<Long> getContainers(List<OmKeyLocationInfoGroup> locationVersions, Set<Long> targetContainerIds) {
Set<Long> keyContainers = new HashSet<>();
keyInfo.getKeyLocationVersions().forEach(
locationVersions.forEach(
e -> e.getLocationList().forEach(
blk -> {
long cid = blk.getBlockID().getContainerID();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.apache.hadoop.hdds.client.StandaloneReplicationConfig;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
import org.apache.hadoop.ozone.OzoneConsts;
import org.apache.hadoop.ozone.debug.OzoneDebug;
import org.apache.hadoop.ozone.om.OMMetadataManager;
import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
Expand All @@ -41,6 +42,8 @@
import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfo;
import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfoGroup;
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.OmVolumeArgs;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.KeyInfo;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.PartKeyInfo;
Expand Down Expand Up @@ -189,6 +192,18 @@ public void testContainerToKeyMappingWithMPUOnlyFileNames() {
assertThat(output).contains("/vol1/obs-bucket/mpuKey/test-upload-id");
}

@Test
public void testContainerToKeyMappingWithSplitSchemaMPU() {
int exitCode = execute("--containers", String.valueOf(CONTAINER_ID_4), "--in-progress");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a bug, but using a distinct CONTAINER_ID_5 for the new tests would make the split-schema test self-contained and easier to reason about in future.

CONTAINER_ID_4 for both legacy and split MPU testContainerToKeyMappingWithSplitSchemaMPU

assertEquals(0, exitCode);

String output = outWriter.toString();

assertThat(output).contains("\"" + CONTAINER_ID_4 + "\"");
assertThat(output).contains("\"openKeys\"");
assertThat(output).contains("/vol1/obs-bucket/splitMpuKey/split-upload-id");
}

@Test

public void testNonExistentContainer() {
Expand Down Expand Up @@ -292,6 +307,7 @@ private void createTestData() throws Exception {

// Create MPU (multipart upload) for OBS bucket with parts in container 5
createMultipartUpload();
createSplitSchemaMultipartUpload();
}

/**
Expand Down Expand Up @@ -339,6 +355,46 @@ private void createMultipartUpload() throws Exception {
omMetadataManager.getMultipartInfoTable().put(mpuKey, mpuInfo);
}

/**
* Helper method to create a split-schema multipart upload with parts in
* multipartPartsTable.
*/
private void createSplitSchemaMultipartUpload() throws Exception {
String mpuKeyName = "splitMpuKey";
String uploadId = "split-upload-id";

OmKeyInfo part1Info = new OmKeyInfo.Builder(createOBSKeyInfo(
mpuKeyName + "/" + uploadId + "/part-1", MPU_PART1_ID + 10, CONTAINER_ID_4))
.addMetadata(OzoneConsts.ETAG, "etag-1")
.build();
OmMultipartPartInfo partInfo1 = OmMultipartPartInfo.from(
mpuKeyName + "/" + uploadId + "/part-1", 1, part1Info);

OmKeyInfo part2Info = new OmKeyInfo.Builder(createOBSKeyInfo(
mpuKeyName + "/" + uploadId + "/part-2", MPU_PART2_ID + 10, CONTAINER_ID_4))
.addMetadata(OzoneConsts.ETAG, "etag-2")
.build();
OmMultipartPartInfo partInfo2 = OmMultipartPartInfo.from(
mpuKeyName + "/" + uploadId + "/part-2", 2, part2Info);

omMetadataManager.getMultipartPartsTable().put(OmMultipartPartKey.of(uploadId, 1), partInfo1);
omMetadataManager.getMultipartPartsTable().put(OmMultipartPartKey.of(uploadId, 2), partInfo2);

OmMultipartKeyInfo mpuInfo = new OmMultipartKeyInfo.Builder()
.setUploadID(uploadId)
.setCreationTime(System.currentTimeMillis())
.setReplicationConfig(StandaloneReplicationConfig.getInstance(HddsProtos.ReplicationFactor.ONE))
.setSchemaVersion(OmMultipartKeyInfo.SPLIT_PARTS_TABLE_SCHEMA_VERSION)
.setObjectID(MPU_KEY_ID + 10)
.setParentID(0)
.setUpdateID(1)
.build();

String mpuKey = omMetadataManager.getMultipartKey(
VOLUME_NAME, OBS_BUCKET_NAME, mpuKeyName, uploadId);
omMetadataManager.getMultipartInfoTable().put(mpuKey, mpuInfo);
}

/**
* Helper method to create OmKeyInfo with a block in specified container (FSO).
*/
Expand Down Expand Up @@ -386,6 +442,8 @@ private OmKeyInfo createOBSKeyInfo(String keyName, long objectId, long container
.setDataSize(1024)
.setObjectID(objectId)
.setUpdateID(1)
.setCreationTime(System.currentTimeMillis())
.setModificationTime(System.currentTimeMillis())
.addOmKeyLocationInfoGroup(locationGroup)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1559,8 +1559,13 @@ public List<ExpiredMultipartUploadsBucket> getExpiredMultipartUploads(
expiredMPUs.get(mapKey)
.addMultipartUploads(builder.setName(dbMultipartInfoKey)
.build());
numParts += omMultipartKeyInfo.getPartKeyInfoMap().size();
// TODO: Add the expired part handling from the new table when the complete flow is done

if (omMultipartKeyInfo.getSchemaVersion()
== OmMultipartKeyInfo.SPLIT_PARTS_TABLE_SCHEMA_VERSION) {
numParts += OMMultipartUploadUtils.countParts(this, expiredMultipartUpload.getUploadId());
} else {
numParts += omMultipartKeyInfo.getPartKeyInfoMap().size();
}
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,16 @@ public static SortedMap<Integer, OmMultipartPartInfo> scanParts(
return parts;
}

/**
* Count the multipart parts belonging to a given upload in the split
* multipartPartsTable, honouring cache tombstones and pending commits. The
* count therefore matches the set of parts a subsequent abort/cleanup would
* process, which makes it suitable for batch sizing.
*/
public static int countParts(OMMetadataManager omMetadataManager, String uploadId) throws IOException {
return scanParts(omMetadataManager, uploadId).size();
}

public static List<OmMultipartPartKey> getPartKeys(String uploadId,
SortedMap<Integer, OmMultipartPartInfo> parts) {
List<OmMultipartPartKey> partKeys = new ArrayList<>(parts.size());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1138,11 +1138,23 @@ private void processMultipartUploads(OmBucketInfo bucketInfo, List<OmLCRule> rul
abortExpiredMultipartUploadsAndClear(bucketInfo, expiredUploads);
}

// Get part count for this MPU (at least 1 even if no parts uploaded yet)
int partCount = Math.max(1, mpuKeyInfo.getPartKeyInfoMap().size());
expiredUploads.add(upload, partCount);
// Split-schema MPUs keep parts in multipartPartsTable (the embedded map
// is empty); legacy MPUs use the embedded map. An MPU with no uploaded
// parts is valid (S3 allows aborting it with an empty parts list).
int uploadedParts;
try {
uploadedParts = mpuKeyInfo.getSchemaVersion()
== OmMultipartKeyInfo.SPLIT_PARTS_TABLE_SCHEMA_VERSION
? OMMultipartUploadUtils.countParts(omMetadataManager, upload.getUploadId())
: mpuKeyInfo.getPartKeyInfoMap().size();
} catch (IOException e) {
LOG.warn("Failed to count parts for MPU {}/{}/{} uploadId {}, skipping",
volumeName, bucketName, keyName, upload.getUploadId(), e);
break;
}
expiredUploads.add(upload, uploadedParts);
LOG.debug("Multipart upload {}/{}/{} with uploadId {} ({} parts) will be aborted",
volumeName, bucketName, keyName, upload.getUploadId(), partCount);
volumeName, bucketName, keyName, upload.getUploadId(), uploadedParts);
break;
}
}
Expand Down
Loading
Loading