List<ChunkInfo> getChunksToStitch(CompleteMultipartUpload completeMultipartUpload) throws RestServiceException {
    // Get parts in order from CompleteMultipartUpload, deserialize each part id to get data chunk ids.
    List<ChunkInfo> chunkInfos = new ArrayList<>();
    try {
      // sort the list in order
      List<Part> sortedParts = Arrays.asList(completeMultipartUpload.getPart());
      Collections.sort(sortedParts, Comparator.comparingInt(Part::getPartNumber));

      String reservedMetadataId = null;
      for (Part part : sortedParts) {
        PutBlobMetaInfo putBlobMetaInfoObj = PutBlobMetaInfo.deserialize(part.geteTag());
        // reservedMetadataId can be null. but if it's not null, they are supposed to be the same
        String reserved = putBlobMetaInfoObj.getReservedMetadataChunkId();
        if (reservedMetadataId == null) {
          reservedMetadataId = reserved;
        }
        if (reserved != null && !reserved.equals(reservedMetadataId)) {
          String error = "Reserved ID are different " + completeMultipartUpload;
          throw new RestServiceException(error, RestServiceErrorCode.BadRequest);
        }
        long expirationTimeInMs = -1;

        List<Pair<String, Long>> chunks = putBlobMetaInfoObj.getOrderedChunkIdSizeList();
        for (int i = 0; i < putBlobMetaInfoObj.getNumChunks(); i++) {
          String blobId = chunks.get(i).getFirst();
          long chunkSize = chunks.get(i).getSecond();

          ChunkInfo chunk = new ChunkInfo(blobId, chunkSize, expirationTimeInMs, reservedMetadataId);
          chunkInfos.add(chunk);
        }
      }
    } catch (IOException e) {
      String error = "Could not parse xml request body " + completeMultipartUpload;
      throw new RestServiceException(error, e, RestServiceErrorCode.BadRequest);
    }
    return chunkInfos;
  }
Test the S3 Multipart upload
1. Part Upload: Test the PutBlob with skipCompositeChunk option. We don't generate composite chunk for it.
Instead, we return PutBlobMetaInfo which includes the data chunk list.
No matter if router enables "reserved metadata chunk" feature, S3 part upload doesn't reserve metadata chunk.
2. Stitch Blob: It's called in completeMultipartUpload. It stitches all data chunks.
@throws Exception
ambry-router/src/test/java/com/github/ambry/router/S3NonBlockingRouterTest.java