private Pair<List<String>, byte[]> uploadDataChunksAndVerify(Account account, Container container, Long chunkBlobTtl,
      int... chunkSizes) throws Exception {
    IdSigningService idSigningService = new AmbryIdSigningService();
    HttpHeaders chunkUploadHeaders = new DefaultHttpHeaders();
    chunkUploadHeaders.add(RestUtils.Headers.URL_TYPE, RestMethod.POST.name());
    chunkUploadHeaders.add(RestUtils.Headers.CHUNK_UPLOAD, "true");
    setAmbryHeadersForPut(chunkUploadHeaders, chunkBlobTtl, !container.isCacheable(), "chunkUploader",
        "application/octet-stream", "stitchedUploadTest", account.getName(), container.getName());

    // POST
    // Get signed URL
    FullHttpRequest httpRequest = buildRequest(HttpMethod.GET, Operations.GET_SIGNED_URL, chunkUploadHeaders, null);
    ResponseParts responseParts = nettyClient.sendRequest(httpRequest, null, null).get();
    HttpResponse response = getHttpResponse(responseParts);
    assertEquals("Unexpected response status", HttpResponseStatus.OK, response.status());
    verifyTrackingHeaders(response);
    String signedPostUrl = response.headers().get(RestUtils.Headers.SIGNED_URL);
    assertNotNull("Did not get a signed POST URL", signedPostUrl);
    assertNoContent(responseParts.queue, 1);

    List<String> signedChunkIds = new ArrayList<>();
    ByteArrayOutputStream fullContentStream = new ByteArrayOutputStream();
    URI uri = new URI(signedPostUrl);
    for (int chunkSize : chunkSizes) {
      byte[] contentArray = TestUtils.getRandomBytes(chunkSize);
      ByteBuffer content = ByteBuffer.wrap(contentArray);
      // Use signed URL to POST
      httpRequest = buildRequest(HttpMethod.POST, uri.getPath() + "?" + uri.getQuery(), null, content);
      responseParts = nettyClient.sendRequest(httpRequest, null, null).get();
      String signedId = verifyPostAndReturnBlobId(responseParts, chunkSize, false);
      assertTrue("Blob ID for chunk upload must be signed", idSigningService.isIdSigned(signedId.substring(1)));
      Pair<String, Map<String, String>> idAndMetadata = idSigningService.parseSignedId(signedId.substring(1));
      // Inspect metadata fields
      String chunkUploadSession = idAndMetadata.getSecond().get(RestUtils.Headers.SESSION);
      assertNotNull("x-ambry-chunk-upload-session should be present in signed ID", chunkUploadSession);
      String blobSize = idAndMetadata.getSecond().get(RestUtils.Headers.BLOB_SIZE);
      assertNotNull("x-ambry-blob-size should be present in signed ID", blobSize);
      assertEquals("wrong size value in signed id", content.capacity(), Long.parseLong(blobSize));
      HttpHeaders expectedGetHeaders = new DefaultHttpHeaders().add(chunkUploadHeaders);
      // Use signed ID and blob ID for GET request
      expectedGetHeaders.add(RestUtils.Headers.BLOB_SIZE, content.capacity());
      // Blob TTL for chunk upload default is chunkUploadInitialChunkTtlSecs if x-ambry-ttl == null.
      if (chunkBlobTtl == null) {
        expectedGetHeaders.set(RestUtils.Headers.TTL, FRONTEND_CONFIG.chunkUploadInitialChunkTtlSecs);
      } else {
        expectedGetHeaders.set(RestUtils.Headers.TTL, chunkBlobTtl);
      }
      expectedGetHeaders.set(RestUtils.Headers.LIFE_VERSION, "0");
      for (String id : new String[]{signedId, idAndMetadata.getFirst()}) {
        getBlobAndVerify(id, null, GetOption.None, false, expectedGetHeaders, !container.isCacheable(), content,
            account.getName(), container.getName(), container);
        getBlobInfoAndVerify(id, GetOption.None, expectedGetHeaders, !container.isCacheable(), account.getName(),
            container.getName(), null, container);
      }
      signedChunkIds.add(addClusterPrefix ? "/" + CLUSTER_NAME + signedId : signedId);
      fullContentStream.write(contentArray);
    }
    return new Pair<>(signedChunkIds, fullContentStream.toByteArray());
  }
Upload data chunks using chunk upload signed URL.
@param account the {@link Account} to upload into.
@param container the {@link Container} to upload into.
@param chunkBlobTtl
@param chunkSizes The sizes for each data chunk to upload.
@return the list of signed chunk IDs for the uploaded chunks and an array containing the concatenated content of
        the data chunks.
@throws Exception
ambry-frontend/src/integration-test/java/com/github/ambry/frontend/FrontendIntegrationTest.java