use of com.github.ambry.commons.Callback in project ambry by linkedin.
the class AmbrySecurityServiceTest method testExceptionCasesProcessResponse.
/**
* Tests exception cases for
* {@link SecurityService#processResponse(RestRequest, RestResponseChannel, BlobInfo, Callback)}
* @param restMethod the {@link RestMethod} of the request to be made
* @param restResponseChannel the {@link RestResponseChannel} to write responses over.
* @param blobInfo the {@link BlobInfo} to be used for the {@link RestRequest}
* @param expectedErrorCode the {@link RestServiceErrorCode} expected in the exception returned.
* @throws Exception
*/
private void testExceptionCasesProcessResponse(RestMethod restMethod, RestResponseChannel restResponseChannel, BlobInfo blobInfo, RestServiceErrorCode expectedErrorCode) throws Exception {
ThrowingConsumer<ExecutionException> errorAction = e -> {
Assert.assertTrue("Exception should have been an instance of RestServiceException", e.getCause() instanceof RestServiceException);
RestServiceException re = (RestServiceException) e.getCause();
Assert.assertEquals("Unexpected RestServerErrorCode (Future)", expectedErrorCode, re.getErrorCode());
};
RestRequest restRequest = createRestRequest(restMethod, "/", null);
TestUtils.assertException(ExecutionException.class, () -> securityService.processResponse(restRequest, restResponseChannel, blobInfo).get(), errorAction);
}
use of com.github.ambry.commons.Callback in project ambry by linkedin.
the class FrontendRestRequestService method securityPostProcessRequestCallback.
/**
* Build a callback to use for {@link SecurityService#postProcessRequest}. This callback forwards request to the
* {@link Router} once ID conversion is completed. In the case of some sub-resources
* (e.g., {@link SubResource#Replicas}), the request is completed and not forwarded to the {@link Router}.
* @param convertedId the converted blob ID to use in router requests.
* @param restRequest the {@link RestRequest}.
* @param restResponseChannel the {@link RestResponseChannel}.
* @param getCallback the {@link GetCallback} to use if this is a {@link RestMethod#GET} request, or null for other
* request types.
* @param headCallback the {@link HeadCallback} to use if this is a {@link RestMethod#HEAD} request, or null for other
* request types.
* @param deleteCallback the {@link DeleteCallback} to use if this is a {@link RestMethod#DELETE} request, or null for
* other request types.
* @return the {@link Callback} to use.
*/
private Callback<Void> securityPostProcessRequestCallback(String convertedId, RestRequest restRequest, RestResponseChannel restResponseChannel, GetCallback getCallback, HeadCallback headCallback, DeleteCallback deleteCallback) {
Callback<ReadableStreamChannel> completionCallback = (result, exception) -> submitResponse(restRequest, restResponseChannel, result, exception);
RestMethod restMethod = restRequest.getRestMethod();
AsyncOperationTracker.Metrics metrics;
switch(restMethod) {
case GET:
metrics = frontendMetrics.getSecurityPostProcessRequestMetrics;
break;
case HEAD:
metrics = frontendMetrics.headSecurityPostProcessRequestMetrics;
break;
case DELETE:
metrics = frontendMetrics.deleteSecurityPostProcessRequestMetrics;
break;
default:
throw new IllegalStateException("Unrecognized RestMethod: " + restMethod);
}
return FrontendUtils.buildCallback(metrics, result -> {
ReadableStreamChannel response = null;
switch(restMethod) {
case GET:
SubResource subResource = getRequestPath(restRequest).getSubResource();
// inject encryption metrics if need be
if (BlobId.isEncrypted(convertedId)) {
RestRequestMetrics restRequestMetrics = getMetricsGroupForGet(frontendMetrics, subResource).getRestRequestMetrics(restRequest.isSslUsed(), true);
restRequest.getMetricsTracker().injectMetrics(restRequestMetrics);
}
if (subResource == null) {
getCallback.markStartTime();
router.getBlob(convertedId, getCallback.options, getCallback, QuotaUtils.buildQuotaChargeCallback(restRequest, quotaManager, true));
} else {
switch(subResource) {
case BlobInfo:
case UserMetadata:
case Segment:
getCallback.markStartTime();
router.getBlob(convertedId, getCallback.options, getCallback, QuotaUtils.buildQuotaChargeCallback(restRequest, quotaManager, true));
break;
case Replicas:
response = getReplicasHandler.getReplicas(convertedId, restResponseChannel);
break;
}
}
break;
case HEAD:
GetOption getOption = getGetOption(restRequest, frontendConfig.defaultRouterGetOption);
// inject encryption metrics if need be
if (BlobId.isEncrypted(convertedId)) {
RestRequestMetrics requestMetrics = frontendMetrics.headBlobMetricsGroup.getRestRequestMetrics(restRequest.isSslUsed(), true);
restRequest.getMetricsTracker().injectMetrics(requestMetrics);
}
headCallback.markStartTime();
router.getBlob(convertedId, new GetBlobOptionsBuilder().operationType(GetBlobOptions.OperationType.BlobInfo).getOption(getOption).restRequest(restRequest).build(), headCallback, QuotaUtils.buildQuotaChargeCallback(restRequest, quotaManager, false));
break;
case DELETE:
deleteCallback.markStartTime();
router.deleteBlob(convertedId, getHeader(restRequest.getArgs(), Headers.SERVICE_ID, false), deleteCallback, QuotaUtils.buildQuotaChargeCallback(restRequest, quotaManager, false));
break;
default:
throw new IllegalStateException("Unrecognized RestMethod: " + restMethod);
}
if (response != null) {
completionCallback.onCompletion(response, null);
}
}, restRequest.getUri(), logger, completionCallback);
}
use of com.github.ambry.commons.Callback in project ambry by linkedin.
the class MySqlNamedBlobDb method executeTransactionAsync.
/**
* Run a transaction on a thread pool and handle common logic surrounding looking up account metadata and error
* handling. Eventually this will handle retries.
* @param <T> the return type of the {@link Transaction}.
* @param accountName the account name for the transaction.
* @param containerName the container name for the transaction.
* @param autoCommit true if each statement execution should be its own transaction. If set to false, this helper will
* handle calling commit/rollback.
* @param transaction the {@link Transaction} to run. This can either be a read only query or include DML.
* @param transactionStateTracker the {@link TransactionStateTracker} to describe the retry strategy.
* @return a {@link CompletableFuture} that will eventually contain the result of the transaction or an exception.
*/
private <T> CompletableFuture<T> executeTransactionAsync(String accountName, String containerName, boolean autoCommit, Transaction<T> transaction, TransactionStateTracker transactionStateTracker) {
CompletableFuture<T> future = new CompletableFuture<>();
// Look up account and container IDs. This is common logic needed for all types of transactions.
Account account = accountService.getAccountByName(accountName);
if (account == null) {
future.completeExceptionally(new RestServiceException("Account not found: " + accountName, RestServiceErrorCode.NotFound));
return future;
}
Container container = account.getContainerByName(containerName);
if (container == null) {
future.completeExceptionally(new RestServiceException("Container not found: " + containerName, RestServiceErrorCode.NotFound));
return future;
}
Callback<T> finalCallback = (result, exception) -> {
if (exception != null) {
future.completeExceptionally(exception);
} else {
future.complete(result);
}
};
// TODO consider introducing CompletableFuture support in RetryExecutor so that we can use only futures, no callback
if (transactionStateTracker != null) {
retryExecutor.runWithRetries(RetryPolicies.fixedBackoffPolicy(transactionExecutors.size(), 0), callback -> {
String datacenter = transactionStateTracker.getNextDatacenter();
transactionExecutors.get(datacenter).executeTransaction(container, autoCommit, transaction, callback);
}, transactionStateTracker::processFailure, finalCallback);
} else {
transactionExecutors.get(localDatacenter).executeTransaction(container, autoCommit, transaction, finalCallback);
}
return future;
}
use of com.github.ambry.commons.Callback in project ambry by linkedin.
the class CloudOperationTest method getBlobAndAssertSuccess.
/**
* Construct GetBlob operations with appropriate callbacks, then poll those operations until they complete,
* and ensure that the whole blob data is read out and the contents match.
* @param blobId id of the blob to get
* @param expectedLifeVersion the expected lifeVersion from get operation.
* @param expectedBlobSize the expected blob size
* @param expectedBlobProperties the expected {@link BlobProperties} for the blob.
* @param expectedUserMetadata the expected user meta data
* @param expectPutContent the expected blob content
* @param options options of the get blob operation
* @throws Exception Any unexpected exception
*/
private void getBlobAndAssertSuccess(final BlobId blobId, final short expectedLifeVersion, final int expectedBlobSize, final BlobProperties expectedBlobProperties, final byte[] expectedUserMetadata, final byte[] expectPutContent, final GetBlobOptionsInternal options) throws Exception {
final CountDownLatch readCompleteLatch = new CountDownLatch(1);
final AtomicLong readCompleteResult = new AtomicLong(0);
// callback to compare the data
Callback<GetBlobResultInternal> callback = (result, exception) -> {
Assert.assertNull("Shouldn't have exception", exception);
try {
BlobInfo blobInfo;
switch(options.getBlobOptions.getOperationType()) {
case All:
Assert.assertFalse("not supposed to be raw mode", options.getBlobOptions.isRawMode());
blobInfo = result.getBlobResult.getBlobInfo();
Assert.assertTrue("Blob properties must be the same", RouterTestHelpers.arePersistedFieldsEquivalent(expectedBlobProperties, blobInfo.getBlobProperties()));
Assert.assertEquals("Blob size should in received blobProperties should be the same as actual", expectedBlobSize, blobInfo.getBlobProperties().getBlobSize());
Assert.assertArrayEquals("User metadata must be the same", expectedUserMetadata, blobInfo.getUserMetadata());
Assert.assertEquals("LifeVersion mismatch", expectedLifeVersion, blobInfo.getLifeVersion());
break;
case Data:
Assert.assertNull("Unexpected blob info in operation result", result.getBlobResult.getBlobInfo());
break;
case BlobInfo:
blobInfo = result.getBlobResult.getBlobInfo();
Assert.assertTrue("Blob properties must be the same", RouterTestHelpers.arePersistedFieldsEquivalent(expectedBlobProperties, blobInfo.getBlobProperties()));
Assert.assertEquals("Blob size should in received blobProperties should be the same as actual", expectedBlobSize, blobInfo.getBlobProperties().getBlobSize());
Assert.assertNull("Unexpected blob data in operation result", result.getBlobResult.getBlobDataChannel());
Assert.assertEquals("LifeVersion mismatch", expectedLifeVersion, blobInfo.getLifeVersion());
}
} catch (Throwable e) {
Assert.fail("Shouldn't receive exception here");
}
if (options.getBlobOptions.getOperationType() != GetBlobOptions.OperationType.BlobInfo) {
final ByteBufferAsyncWritableChannel asyncWritableChannel = new ByteBufferAsyncWritableChannel();
Utils.newThread(() -> {
Future<Long> readIntoFuture = result.getBlobResult.getBlobDataChannel().readInto(asyncWritableChannel, null);
assertBlobReadSuccess(options.getBlobOptions, readIntoFuture, asyncWritableChannel, result.getBlobResult.getBlobDataChannel(), readCompleteLatch, readCompleteResult, expectedBlobSize, expectPutContent);
}, false).start();
} else {
readCompleteLatch.countDown();
}
};
// create GetBlobOperation
final Map<Integer, GetOperation> correlationIdToGetOperation = new HashMap<>();
final RequestRegistrationCallback<GetOperation> requestRegistrationCallback = new RequestRegistrationCallback<>(correlationIdToGetOperation);
NonBlockingRouter.currentOperationsCount.incrementAndGet();
GetBlobOperation op = new GetBlobOperation(routerConfig, routerMetrics, mockClusterMap, responseHandler, blobId, options, callback, routerCallback, blobIdFactory, null, null, null, time, false, null);
requestRegistrationCallback.setRequestsToSend(new ArrayList<>());
// Wait operation to complete
while (!op.isOperationComplete()) {
op.poll(requestRegistrationCallback);
List<ResponseInfo> responses = sendAndWaitForResponses(requestRegistrationCallback.getRequestsToSend());
for (ResponseInfo responseInfo : responses) {
GetResponse getResponse = RouterUtils.extractResponseAndNotifyResponseHandler(responseHandler, routerMetrics, responseInfo, stream -> GetResponse.readFrom(stream, mockClusterMap), response -> {
ServerErrorCode serverError = response.getError();
if (serverError == ServerErrorCode.No_Error) {
serverError = response.getPartitionResponseInfoList().get(0).getErrorCode();
}
return serverError;
});
op.handleResponse(responseInfo, getResponse);
responseInfo.release();
}
}
readCompleteLatch.await();
Assert.assertTrue("Operation should be complete at this time", op.isOperationComplete());
// Ensure that a ChannelClosed exception is not set when the ReadableStreamChannel is closed correctly.
Assert.assertNull("Callback operation exception should be null", op.getOperationException());
if (options.getBlobOptions.getOperationType() != GetBlobOptions.OperationType.BlobInfo && !options.getBlobOptions.isRawMode() && !options.getChunkIdsOnly) {
int sizeWritten = expectedBlobSize;
if (options.getBlobOptions.getRange() != null) {
ByteRange range = options.getBlobOptions.getRange().toResolvedByteRange(expectedBlobSize, options.getBlobOptions.resolveRangeOnEmptyBlob());
sizeWritten = (int) range.getRangeSize();
}
Assert.assertEquals("Size read must equal size written", sizeWritten, readCompleteResult.get());
}
}
use of com.github.ambry.commons.Callback in project ambry by linkedin.
the class GetBlobOperationTest method testInstantiation.
/**
* Test {@link GetBlobOperation} instantiation and validate the get methods.
*/
@Test
public void testInstantiation() {
Callback<GetBlobResultInternal> getRouterCallback = new Callback<GetBlobResultInternal>() {
@Override
public void onCompletion(GetBlobResultInternal result, Exception exception) {
// no op.
}
};
blobId = new BlobId(routerConfig.routerBlobidCurrentVersion, BlobId.BlobIdType.NATIVE, mockClusterMap.getLocalDatacenterId(), Utils.getRandomShort(TestUtils.RANDOM), Utils.getRandomShort(TestUtils.RANDOM), mockClusterMap.getWritablePartitionIds(MockClusterMap.DEFAULT_PARTITION_CLASS).get(0), false, BlobId.BlobDataType.DATACHUNK);
blobIdStr = blobId.getID();
// test a good case
// operationCount is not incremented here as this operation is not taken to completion.
GetBlobOperation op = new GetBlobOperation(routerConfig, routerMetrics, mockClusterMap, responseHandler, blobId, new GetBlobOptionsInternal(new GetBlobOptionsBuilder().build(), false, routerMetrics.ageAtGet), getRouterCallback, routerCallback, blobIdFactory, kms, cryptoService, cryptoJobHandler, time, false, quotaChargeCallback);
Assert.assertEquals("Callbacks must match", getRouterCallback, op.getCallback());
Assert.assertEquals("Blob ids must match", blobIdStr, op.getBlobIdStr());
// test the case where the tracker type is bad
Properties properties = getDefaultNonBlockingRouterProperties(true);
properties.setProperty("router.get.operation.tracker.type", "NonExistentTracker");
RouterConfig badConfig = new RouterConfig(new VerifiableProperties(properties));
try {
new GetBlobOperation(badConfig, routerMetrics, mockClusterMap, responseHandler, blobId, new GetBlobOptionsInternal(new GetBlobOptionsBuilder().build(), false, routerMetrics.ageAtGet), getRouterCallback, routerCallback, blobIdFactory, kms, cryptoService, cryptoJobHandler, time, false, quotaChargeCallback);
Assert.fail("Instantiation of GetBlobOperation with an invalid tracker type must fail");
} catch (IllegalArgumentException e) {
// expected. Nothing to do.
}
}
Aggregations