Search in sources :

Example 6 with ByteRange

use of com.github.ambry.router.ByteRange in project ambry by linkedin.

the class RestUtils method buildByteRange.

/**
 * Build a {@link ByteRange} given a Range header value. This method can parse the following Range
 * header syntax:
 * {@code Range:bytes=byte_range} where {@code bytes=byte_range} supports the following range syntax:
 * <ul>
 *   <li>For bytes {@code {a}} through {@code {b}} inclusive: {@code bytes={a}-{b}}</li>
 *   <li>For all bytes including and after {@code {a}}: {@code bytes={a}-}</li>
 *   <li>For the last {@code {b}} bytes of a file: {@code bytes=-{b}}</li>
 * </ul>
 * @param rangeHeaderValue the value of the Range header.
 * @return The {@link ByteRange} parsed from the arguments.
 * @throws RestServiceException if no range header was found, or if a valid range could not be parsed from the header
 *                              value,
 */
public static ByteRange buildByteRange(String rangeHeaderValue) throws RestServiceException {
    if (!rangeHeaderValue.startsWith(BYTE_RANGE_PREFIX)) {
        throw new RestServiceException("Invalid byte range syntax; does not start with '" + BYTE_RANGE_PREFIX + "'", RestServiceErrorCode.InvalidArgs);
    }
    ByteRange range;
    try {
        int hyphenIndex = rangeHeaderValue.indexOf('-', BYTE_RANGE_PREFIX.length());
        String startOffsetStr = rangeHeaderValue.substring(BYTE_RANGE_PREFIX.length(), hyphenIndex);
        String endOffsetStr = rangeHeaderValue.substring(hyphenIndex + 1);
        if (startOffsetStr.isEmpty()) {
            range = ByteRanges.fromLastNBytes(Long.parseLong(endOffsetStr));
        } else if (endOffsetStr.isEmpty()) {
            range = ByteRanges.fromStartOffset(Long.parseLong(startOffsetStr));
        } else {
            range = ByteRanges.fromOffsetRange(Long.parseLong(startOffsetStr), Long.parseLong(endOffsetStr));
        }
    } catch (Exception e) {
        throw new RestServiceException("Valid byte range could not be parsed from Range header value: " + rangeHeaderValue, RestServiceErrorCode.InvalidArgs);
    }
    return range;
}
Also used : ByteRange(com.github.ambry.router.ByteRange) ParseException(java.text.ParseException)

Example 7 with ByteRange

use of com.github.ambry.router.ByteRange in project ambry by linkedin.

the class FrontendTestUrlSigningServiceFactory method doPostGetHeadUpdateDeleteUndeleteTest.

// postGetHeadUpdateDeleteTest() helpers
/**
 * Tests blob POST, GET, HEAD, TTL Update and DELETE operations on the given {@code container}.
 * @param toPostAccount the {@link Account} to use in post headers. Can be {@code null} if only using service ID.
 * @param toPostContainer the {@link Container} to use in post headers. Can be {@code null} if only using service ID.
 * @param serviceId the serviceId to use for the POST
 * @param isPrivate the isPrivate flag to pass as part of the POST
 * @param expectedAccount the {@link Account} details that are eventually expected to be populated.
 * @param expectedContainer the {@link Container} details that are eventually expected to be populated.
 * @throws Exception
 */
private void doPostGetHeadUpdateDeleteUndeleteTest(Account toPostAccount, Container toPostContainer, String serviceId, boolean isPrivate, Account expectedAccount, Container expectedContainer) throws Exception {
    ByteBuffer content = ByteBuffer.wrap(TestUtils.getRandomBytes(CONTENT_LENGTH));
    String contentType = "application/octet-stream";
    String ownerId = "postGetHeadDeleteOwnerID";
    JSONObject headers = new JSONObject();
    String accountNameInPost = toPostAccount != null ? toPostAccount.getName() : null;
    String containerNameInPost = toPostContainer != null ? toPostContainer.getName() : null;
    setAmbryHeadersForPut(headers, TTL_SECS, isPrivate, serviceId, contentType, ownerId, accountNameInPost, containerNameInPost, null);
    Map<String, String> userMetadata = new HashMap<>();
    userMetadata.put(RestUtils.Headers.USER_META_DATA_HEADER_PREFIX + "key1", "value1");
    userMetadata.put(RestUtils.Headers.USER_META_DATA_HEADER_PREFIX + "key2", "value2");
    RestUtilsTest.setUserMetadataHeaders(headers, userMetadata);
    String blobId = postBlobAndVerify(headers, content, expectedAccount, expectedContainer);
    headers.put(RestUtils.Headers.BLOB_SIZE, (long) CONTENT_LENGTH);
    getBlobAndVerify(blobId, null, null, headers, content, expectedAccount, expectedContainer);
    getBlobAndVerify(blobId, null, GetOption.None, headers, content, expectedAccount, expectedContainer);
    getHeadAndVerify(blobId, null, null, headers, expectedAccount, expectedContainer);
    getHeadAndVerify(blobId, null, GetOption.None, headers, expectedAccount, expectedContainer);
    ByteRange range = ByteRanges.fromStartOffset(ThreadLocalRandom.current().nextLong(CONTENT_LENGTH));
    getBlobAndVerify(blobId, range, null, headers, content, expectedAccount, expectedContainer);
    getHeadAndVerify(blobId, range, null, headers, expectedAccount, expectedContainer);
    range = ByteRanges.fromLastNBytes(ThreadLocalRandom.current().nextLong(CONTENT_LENGTH + 1));
    getBlobAndVerify(blobId, range, null, headers, content, expectedAccount, expectedContainer);
    getHeadAndVerify(blobId, range, null, headers, expectedAccount, expectedContainer);
    long random1 = ThreadLocalRandom.current().nextLong(CONTENT_LENGTH);
    long random2 = ThreadLocalRandom.current().nextLong(CONTENT_LENGTH);
    range = ByteRanges.fromOffsetRange(Math.min(random1, random2), Math.max(random1, random2));
    getBlobAndVerify(blobId, range, null, headers, content, expectedAccount, expectedContainer);
    getHeadAndVerify(blobId, range, null, headers, expectedAccount, expectedContainer);
    getNotModifiedBlobAndVerify(blobId, null);
    getUserMetadataAndVerify(blobId, null, headers);
    getBlobInfoAndVerify(blobId, null, headers, expectedAccount, expectedContainer);
    updateBlobTtlAndVerify(blobId, headers, expectedAccount, expectedContainer, false);
    // Before delete, let's undelete it, should fail
    undeleteBlobAndVerify(blobId, headers, content, expectedAccount, expectedContainer, false, RestServiceErrorCode.Conflict);
    deleteBlobAndVerify(blobId, headers, content, expectedAccount, expectedContainer, false);
    // After delete, let's undelete it, should succeed
    undeleteBlobAndVerify(blobId, headers, content, expectedAccount, expectedContainer, false, null);
}
Also used : JSONObject(org.json.JSONObject) HashMap(java.util.HashMap) ByteRange(com.github.ambry.router.ByteRange) ByteBuffer(java.nio.ByteBuffer)

Example 8 with ByteRange

use of com.github.ambry.router.ByteRange in project ambry by linkedin.

the class FrontendTestUrlSigningServiceFactory method verifyGetBlobResponse.

/**
 * Verifies the GET response received for {@code restRequest} on {@code restResponseChannel}.
 * @param restRequest the {@link RestRequest} that was sent.
 * @param restResponseChannel the {@link MockRestResponseChannel} over which response was received.
 * @param range the optional {@link ByteRange} for the request.
 * @param expectedHeaders the expected headers in the response.
 * @param expectedContent the expected content of the blob.
 * @param expectedAccount the expected account in the rest request.
 * @param expectedContainer the expected container in the rest request.
 * @throws JSONException
 * @throws RestServiceException
 */
private void verifyGetBlobResponse(RestRequest restRequest, MockRestResponseChannel restResponseChannel, ByteRange range, JSONObject expectedHeaders, ByteBuffer expectedContent, Account expectedAccount, Container expectedContainer) throws JSONException, RestServiceException {
    assertEquals("Unexpected response status", range == null ? ResponseStatus.Ok : ResponseStatus.PartialContent, restResponseChannel.getStatus());
    checkCommonGetHeadHeaders(restResponseChannel);
    assertEquals(RestUtils.Headers.BLOB_SIZE + " does not match", expectedHeaders.get(RestUtils.Headers.BLOB_SIZE).toString(), restResponseChannel.getHeader(RestUtils.Headers.BLOB_SIZE));
    assertEquals("Content-Type does not match", expectedHeaders.getString(RestUtils.Headers.AMBRY_CONTENT_TYPE), restResponseChannel.getHeader(RestUtils.Headers.CONTENT_TYPE));
    assertEquals("Accept-Ranges not set correctly", "bytes", restResponseChannel.getHeader(RestUtils.Headers.ACCEPT_RANGES));
    assertEquals("Wrong account object in RestRequest's args", expectedAccount, restRequest.getArgs().get(RestUtils.InternalKeys.TARGET_ACCOUNT_KEY));
    assertEquals("Wrong container object in RestRequest's args", expectedContainer, restRequest.getArgs().get(RestUtils.InternalKeys.TARGET_CONTAINER_KEY));
    verifyBlobProperties(expectedHeaders, restResponseChannel);
    verifyUserMetadataHeaders(expectedHeaders, restResponseChannel);
    verifyAccountAndContainerHeaders(restResponseChannel, expectedAccount, expectedContainer);
    byte[] expectedContentArray = expectedContent.array();
    if (range != null) {
        long blobSize = expectedHeaders.getLong(RestUtils.Headers.BLOB_SIZE);
        assertEquals("Content-Range does not match expected", RestUtils.buildContentRangeAndLength(range, blobSize, false).getFirst(), restResponseChannel.getHeader(RestUtils.Headers.CONTENT_RANGE));
        ByteRange resolvedRange = range.toResolvedByteRange(blobSize);
        expectedContentArray = Arrays.copyOfRange(expectedContentArray, (int) resolvedRange.getStartOffset(), (int) resolvedRange.getEndOffset() + 1);
    } else {
        assertNull("Content-Range header should not be set", restResponseChannel.getHeader(RestUtils.Headers.CONTENT_RANGE));
    }
    assertArrayEquals("GET content does not match original content", expectedContentArray, restResponseChannel.getResponseBody());
}
Also used : ByteRange(com.github.ambry.router.ByteRange)

Example 9 with ByteRange

use of com.github.ambry.router.ByteRange in project ambry by linkedin.

the class FrontendIntegrationTest method emptyBlobRangeHandlingTest.

@Test
public void emptyBlobRangeHandlingTest() throws Exception {
    Account refAccount = ACCOUNT_SERVICE.createAndAddRandomAccount();
    Container refContainer = refAccount.getContainerById(Container.DEFAULT_PUBLIC_CONTAINER_ID);
    HttpHeaders headers = new DefaultHttpHeaders();
    setAmbryHeadersForPut(headers, TTL_SECS, !refContainer.isCacheable(), refAccount.getName(), "application/octet-stream", null, refAccount.getName(), refContainer.getName());
    ByteBuffer content = ByteBuffer.allocate(0);
    String blobId = postBlobAndVerify(headers, content, content.capacity());
    ByteRange range = ByteRanges.fromOffsetRange(0, 50);
    headers.add(RestUtils.Headers.BLOB_SIZE, content.capacity());
    headers.add(RestUtils.Headers.LIFE_VERSION, "0");
    // Should get a 416 if x-ambry-resolve-range-on-empty-blob is not set
    HttpRequest httpRequest = buildRequest(HttpMethod.GET, blobId, new DefaultHttpHeaders().add(RestUtils.Headers.RANGE, RestTestUtils.getRangeHeaderString(range)), null);
    ResponseParts responseParts = nettyClient.sendRequest(httpRequest, null, null).get();
    HttpResponse response = getHttpResponse(responseParts);
    assertEquals("Unexpected response status", HttpResponseStatus.REQUESTED_RANGE_NOT_SATISFIABLE, response.status());
    // Should get a 206 if the header is set.
    getBlobAndVerify(blobId, range, null, true, headers, !refContainer.isCacheable(), content, refAccount.getName(), refContainer.getName());
}
Also used : DefaultFullHttpRequest(io.netty.handler.codec.http.DefaultFullHttpRequest) HttpRequest(io.netty.handler.codec.http.HttpRequest) FullHttpRequest(io.netty.handler.codec.http.FullHttpRequest) Account(com.github.ambry.account.Account) HttpHeaders(io.netty.handler.codec.http.HttpHeaders) DefaultHttpHeaders(io.netty.handler.codec.http.DefaultHttpHeaders) Container(com.github.ambry.account.Container) DefaultHttpHeaders(io.netty.handler.codec.http.DefaultHttpHeaders) ByteRange(com.github.ambry.router.ByteRange) HttpResponse(io.netty.handler.codec.http.HttpResponse) ResponseParts(com.github.ambry.rest.NettyClient.ResponseParts) ByteBuffer(java.nio.ByteBuffer) Test(org.junit.Test)

Example 10 with ByteRange

use of com.github.ambry.router.ByteRange in project ambry by linkedin.

the class FrontendIntegrationTestBase method verifyGetBlobResponse.

/**
 * Verifies the GET blob response.
 * @param responseParts the response received from the server.
 * @param range the {@link ByteRange} for the request.
 * @param resolveRangeOnEmptyBlob {@code true} if the {@link RestUtils.Headers#RESOLVE_RANGE_ON_EMPTY_BLOB} header was
 *                                sent.
 * @param expectedHeaders the expected headers in the response.
 * @param isPrivate {@code true} if the blob is private, {@code false} if not.
 * @param expectedContent the expected content of the blob.
 * @param accountName the account name that should be in the response
 * @param containerName the container name that should be in the response
 * @throws RestServiceException
 */
void verifyGetBlobResponse(NettyClient.ResponseParts responseParts, ByteRange range, boolean resolveRangeOnEmptyBlob, HttpHeaders expectedHeaders, boolean isPrivate, ByteBuffer expectedContent, String accountName, String containerName) throws RestServiceException {
    HttpResponse response = getHttpResponse(responseParts);
    assertEquals("Unexpected response status", range == null ? HttpResponseStatus.OK : HttpResponseStatus.PARTIAL_CONTENT, response.status());
    checkCommonGetHeadHeaders(response.headers());
    assertEquals("Content-Type does not match", expectedHeaders.get(RestUtils.Headers.AMBRY_CONTENT_TYPE), response.headers().get(HttpHeaderNames.CONTENT_TYPE));
    assertEquals(RestUtils.Headers.BLOB_SIZE + " does not match", expectedHeaders.get(RestUtils.Headers.BLOB_SIZE), response.headers().get(RestUtils.Headers.BLOB_SIZE));
    assertEquals("Accept-Ranges not set correctly", "bytes", response.headers().get(RestUtils.Headers.ACCEPT_RANGES));
    assertEquals(RestUtils.Headers.LIFE_VERSION + " does not match", expectedHeaders.get(RestUtils.Headers.LIFE_VERSION), response.headers().get(RestUtils.Headers.LIFE_VERSION));
    byte[] expectedContentArray = expectedContent.array();
    if (range != null) {
        long blobSize = Long.parseLong(expectedHeaders.get(RestUtils.Headers.BLOB_SIZE));
        assertEquals("Content-Range header not set correctly", RestUtils.buildContentRangeAndLength(range, blobSize, resolveRangeOnEmptyBlob).getFirst(), response.headers().get(RestUtils.Headers.CONTENT_RANGE));
        ByteRange resolvedRange = range.toResolvedByteRange(blobSize, resolveRangeOnEmptyBlob);
        expectedContentArray = Arrays.copyOfRange(expectedContentArray, (int) resolvedRange.getStartOffset(), (int) resolvedRange.getEndOffset() + 1);
    } else {
        assertNull("Content-Range header should not be set", response.headers().get(RestUtils.Headers.CONTENT_RANGE));
    }
    if (expectedContentArray.length < frontendConfig.chunkedGetResponseThresholdInBytes) {
        assertEquals("Content-length not as expected", expectedContentArray.length, HttpUtil.getContentLength(response));
    }
    verifyCacheHeaders(isPrivate, response, frontendConfig.cacheValiditySeconds);
    byte[] responseContentArray = getContent(responseParts.queue, expectedContentArray.length).array();
    assertArrayEquals("GET content does not match original content", expectedContentArray, responseContentArray);
    assertTrue("Channel should be active", HttpUtil.isKeepAlive(response));
    verifyTrackingHeaders(response);
    verifyBlobProperties(expectedHeaders, isPrivate, response);
    verifyAccountAndContainerHeaders(accountName, containerName, response);
    verifyUserMetadata(expectedHeaders, response, null, null);
    verifyGetRequestCostHeaders(response, expectedContentArray.length);
}
Also used : ByteRange(com.github.ambry.router.ByteRange) HttpResponse(io.netty.handler.codec.http.HttpResponse)

Aggregations

ByteRange (com.github.ambry.router.ByteRange)10 ByteBuffer (java.nio.ByteBuffer)5 DefaultHttpHeaders (io.netty.handler.codec.http.DefaultHttpHeaders)4 HttpHeaders (io.netty.handler.codec.http.HttpHeaders)4 HttpResponse (io.netty.handler.codec.http.HttpResponse)3 ResponseParts (com.github.ambry.rest.NettyClient.ResponseParts)2 DefaultFullHttpRequest (io.netty.handler.codec.http.DefaultFullHttpRequest)2 FullHttpRequest (io.netty.handler.codec.http.FullHttpRequest)2 HttpRequest (io.netty.handler.codec.http.HttpRequest)2 HashMap (java.util.HashMap)2 JSONObject (org.json.JSONObject)2 Account (com.github.ambry.account.Account)1 Container (com.github.ambry.account.Container)1 ParseException (java.text.ParseException)1 ArrayList (java.util.ArrayList)1 Test (org.junit.Test)1